| title | Effect๋ก ๋๊ธฐํํ๊ธฐ |
|---|
์ผ๋ถ ์ปดํฌ๋ํธ์์๋ ์ธ๋ถ ์์คํ ๊ณผ ๋๊ธฐํํด์ผ ํ ์ ์์ต๋๋ค. ์๋ฅผ ๋ค์ด React์ state์ ๊ธฐ์ค์ผ๋ก React์ ์๊ด์๋ ๊ตฌ์ฑ ์์๋ฅผ ์ ์ดํ๊ฑฐ๋, ์๋ฒ ์ฐ๊ฒฐ์ ์ค์ ํ๊ฑฐ๋, ๊ตฌ์ฑ ์์๊ฐ ํ๋ฉด์ ๋ํ๋ ๋ ๋ถ์ ๋ชฉ์ ์ ๋ก๊ทธ๋ฅผ ์ ์กํ ์๋ ์์ต๋๋ค. Effect๋ฅผ ์ฌ์ฉํ๋ฉด ๋ ๋๋ง ํ ํน์ ์ฝ๋๋ฅผ ์คํํ์ฌ React ์ธ๋ถ์ ์์คํ ๊ณผ ์ปดํฌ๋ํธ๋ฅผ ๋๊ธฐํํ ์ ์์ต๋๋ค.
- Effect๊ฐ ๋ฌด์์ธ์ง
- Effect๊ฐ ์ด๋ฒคํธ์ ๋ค๋ฅธ ์
- ์ปดํฌ๋ํธ์์ Effect๋ฅผ ์ ์ธํ๋ ๋ฐฉ๋ฒ
- ๋ถํ์ํ Effect ์ฌ์คํ์ ๊ฑด๋๋ฐ๋ ๋ฐฉ๋ฒ
- ๊ฐ๋ฐ ์ค์ Effect๊ฐ ๋ ๋ฒ ์คํ๋๋ ์ด์ ์ ํด๊ฒฐ ๋ฐฉ๋ฒ
Effect๋ ๋ฌด์์ด๊ณ ์ด๋ฒคํธ์๋ ์ด๋ป๊ฒ ๋ค๋ฅธ๊ฐ์? {/what-are-effects-and-how-are-they-different-from-events/}
Effect์ ๋ํด ์์ธํ ์์๋ณด๊ธฐ ์ ์, ์ปดํฌ๋ํธ ๋ด๋ถ์ 2๊ฐ์ง ๋ก์ง ์ ํ์ ๋ํด ์์์ผ ํฉ๋๋ค.
-
๋ ๋๋ง ์ฝ๋(UI ํํํ๊ธฐ์ ์๊ฐ๋จ)๋ฅผ ์ฃผ๊ดํ๋ ๋ก์ง์ ์ปดํฌ๋ํธ์ ์ต์๋จ์ ์์นํ๋ฉฐ, props์ state๋ฅผ ์ ์ ํ ๋ณํํด ๊ฒฐ๊ณผ์ ์ผ๋ก JSX๋ฅผ ๋ฐํํฉ๋๋ค. ๋ ๋๋ง ์ฝ๋ ๋ก์ง์ ์์ํด์ผ ํฉ๋๋ค. ์ํ ๊ณต์์ฒ๋ผ ๊ฒฐ๊ณผ๋ง ๊ณ์ฐํด์ผ ํ๊ณ , ๊ทธ ์ธ์๋ ์๋ฌด๊ฒ๋ ํ์ง ๋ง์์ผ ํฉ๋๋ค.
-
์ด๋ฒคํธ ํธ๋ค๋ฌ(์ํธ์์ฉ ๋ํ๊ธฐ์ ์๊ฐ๋จ)๋ ๋จ์ํ ๊ณ์ฐ ์ฉ๋๊ฐ ์๋ ๋ฌด์ธ๊ฐ๋ฅผ ํ๋ ์ปดํฌ๋ํธ ๋ด๋ถ์ ์ค์ฒฉ ํจ์์ ๋๋ค. ์ด๋ฒคํธ ํธ๋ค๋ฌ๋ ์ ๋ ฅ ํ๋๋ฅผ ์ ๋ฐ์ดํธํ๊ฑฐ๋, ์ ํ์ ๊ตฌ์ ํ๊ธฐ ์ํด HTTP POST ์์ฒญ์ ๋ณด๋ด๊ฑฐ๋, ์ฌ์ฉ์๋ฅผ ๋ค๋ฅธ ํ๋ฉด์ผ๋ก ์ด๋์ํฌ ์ ์์ต๋๋ค. ์ด๋ฒคํธ ํธ๋ค๋ฌ์๋ ํน์ ์ฌ์ฉ์ ์์ (์: ๋ฒํผ ํด๋ฆญ ๋๋ ์ ๋ ฅ)์ผ๋ก ์ธํด ๋ฐ์ํ๋ "๋ถ์ ํจ๊ณผ"(์ด๋ฌํ ๋ถ์ ํจ๊ณผ๊ฐ ํ๋ก๊ทธ๋จ ์ํ๋ฅผ ๋ณ๊ฒฝํฉ๋๋ค.)๋ฅผ ํฌํจํฉ๋๋ค.
๊ฐ๋์ ์ด๊ฒ์ผ๋ก ์ถฉ๋ถํ์ง ์์ต๋๋ค. ํ๋ฉด์ ๋ณด์ผ ๋๋ง๋ค ์ฑํ
์๋ฒ์ ์ ์ํด์ผ ํ๋ ChatRoom ์ปดํฌ๋ํธ๋ฅผ ์๊ฐํด ๋ณด์ธ์. ์๋ฒ์ ์ ์ํ๋ ๊ฒ์ ์์ํ ๊ณ์ฐ์ด ์๋๊ณ ๋ถ์ ํจ๊ณผ๋ฅผ ๋ฐ์์ํค๊ธฐ ๋๋ฌธ์ ๋ ๋๋ง ์ค์๋ ํ ์ ์์ต๋๋ค. ํ์ง๋ง ํด๋ฆญ ํ ๋ฒ์ผ๋ก ChatRoom์ด ํ์๋๋ ํน์ ์ด๋ฒคํธ๋ ํ๋๋ ์์ต๋๋ค.
Effect๋ ๋ ๋๋ง ์์ฒด์ ์ํด ๋ฐ์ํ๋ ๋ถ์ ํจ๊ณผ๋ฅผ ํน์ ํ๋ ๊ฒ์ผ๋ก, ํน์ ์ด๋ฒคํธ๊ฐ ์๋ ๋ ๋๋ง์ ์ํด ์ง์ ๋ฐ์ํฉ๋๋ค. ์ฑํ ์์ ๋ฉ์์ง๋ฅผ ๋ณด๋ด๋ ๊ฒ์ ์ด๋ฒคํธ์ ๋๋ค. ์๋ํ๋ฉด ์ด๊ฒ์ ์ฌ์ฉ์๊ฐ ํน์ ๋ฒํผ์ ํด๋ฆญํจ์ ๋ฐ๋ผ ์ง์ ์ ์ผ๋ก ๋ฐ์ํฉ๋๋ค. ๊ทธ๋ฌ๋ ์๋ฒ ์ฐ๊ฒฐ ์ค์ ์ Effect์ ๋๋ค. ์๋ํ๋ฉด ์ด๊ฒ์ ์ปดํฌ๋ํธ์ ํ์๋ฅผ ์ฃผ๊ดํ๋ ์ด๋ค ์ํธ ์์ฉ๊ณผ๋ ์๊ด์์ด ๋ฐ์ํด์ผ ํฉ๋๋ค. Effect๋ ์ปค๋ฐ์ด ๋๋ ํ์ ํ๋ฉด ์ ๋ฐ์ดํธ๊ฐ ์ด๋ฃจ์ด์ง๊ณ ๋์ ์คํ๋ฉ๋๋ค. ์ด ์์ ์ด React ์ปดํฌ๋ํธ๋ฅผ ์ธ๋ถ ์์คํ (๋คํธ์ํฌ ๋๋ ์จ๋ํํฐ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ๊ฐ์)๊ณผ ๋๊ธฐํํ๊ธฐ ์ข์ ํ์ด๋ฐ์ ๋๋ค.
์ด ํ ์คํธ์์์ ๋๋ฌธ์ "Effect"๋ ์์์ ์ธ๊ธํ React์ ํนํ๋ ์ ์๋ฅผ ๋ํ๋ด๋ฉฐ, ๊ณง ๋ ๋๋ง์ ์ํ ๋ถ์ ํจ๊ณผ๋ฅผ ์๋ฏธํฉ๋๋ค. ๋ณด๋ค ์ผ๋ฐ์ ์ธ ํ๋ก๊ทธ๋๋ฐ ๊ฐ๋ ์ ์ธ๊ธํ ๋์๋ "๋ถ์ ํจ๊ณผ"๋ผ๊ณ ๋งํ๊ฒ ์ต๋๋ค.
์ปดํฌ๋ํธ์ Effect๋ฅผ ๋ฌด์์ ์ถ๊ฐํ์ง ๋ง์ธ์. Effect๋ ์ฃผ๋ก React ์ฝ๋๋ฅผ ๋ฒ์ด๋ ํน์ ์ธ๋ถ ์์คํ ๊ณผ ๋๊ธฐํํ๊ธฐ ์ํด ์ฌ์ฉ๋ฉ๋๋ค. ์ด๋ ๋ธ๋ผ์ฐ์ API, ์๋ ํํฐ ์์ ฏ, ๋คํธ์ํฌ ๋ฑ์ ํฌํจํฉ๋๋ค. ๋ง์ฝ ๋น์ ์ Effect๊ฐ ๋จ์ํ ๋ค๋ฅธ ์ํ์ ๊ธฐ๋ฐํ์ฌ ์ผ๋ถ ์ํ๋ฅผ ์กฐ์ ํ๋ ๊ฒฝ์ฐ์๋ Effect๊ฐ ํ์ํ์ง ์์ ์ ์์ต๋๋ค.
Effect๋ฅผ ์์ฑํ๊ธฐ ์ํด์๋ ๋ค์ ์ธ ๋จ๊ณ๋ฅผ ๋ฐ๋ฆ ๋๋ค.
- Effect ์ ์ธ. ๊ธฐ๋ณธ์ ์ผ๋ก Effect๋ ๋ชจ๋ commit ์ดํ์ ์คํ๋ฉ๋๋ค.
- Effect ์์กด์ฑ ์ง์ . ๋๋ถ๋ถ์ Effect๋ ๋ชจ๋ ๋ ๋๋ง ํ๊ฐ ์๋ ํ์ํ ๋๋ง ๋ค์ ์คํ๋์ด์ผ ํฉ๋๋ค. ์๋ฅผ ๋ค์ด, ํ์ด๋ ์ธ ์ ๋๋ฉ์ด์ ์ ์ปดํฌ๋ํธ๊ฐ ๋ํ๋ ๋์๋ง ํธ๋ฆฌ๊ฑฐ ๋์ด์ผ ํฉ๋๋ค. ์ฑํ ๋ฐฉ์ ์ฐ๊ฒฐ, ์ฐ๊ฒฐ ํด์ ํ๋ ๊ฒ์ ์ปดํฌ๋ํธ๊ฐ ๋ํ๋๊ฑฐ๋ ์ฌ๋ผ์ง ๋ ๋๋ ์ฑํ ๋ฐฉ์ด ๋ณ๊ฒฝ๋ ๋๋ง ๋ฐ์ํด์ผ ํฉ๋๋ค. ์์กด์ฑ์ ์ง์ ํ์ฌ ์ด๋ฅผ ์ ์ดํ๋ ๋ฐฉ๋ฒ์ ๋ฐฐ์ฐ๊ฒ ๋ ๊ฒ์ ๋๋ค.
- ํ์ํ ๊ฒฝ์ฐ ํด๋ฆฐ์ ํจ์ ์ถ๊ฐ. ์ผ๋ถ Effect๋ ์ํ ์ค์ด๋ ์์ ์ ์ค์ง, ์ทจ์ ๋๋ ์ ๋ฆฌํ๋ ๋ฐฉ๋ฒ์ ์ง์ ํด์ผ ํ ์ ์์ต๋๋ค. ์๋ฅผ ๋ค์ด, "์ฐ๊ฒฐ"์ "์ฐ๊ฒฐ ํด์ "๊ฐ ํ์ํ๋ฉฐ, "๊ตฌ๋ "์ "๊ตฌ๋ ์ทจ์"๊ฐ ํ์ํ๊ณ , "๋ถ๋ฌ์ค๊ธฐ(fetch)"๋ "์ทจ์" ๋๋ "๋ฌด์"๊ฐ ํ์ํฉ๋๋ค. ์ด๋ฐ ๊ฒฝ์ฐ์ Effect์์ *ํด๋ฆฐ์ ํจ์(cleanup function)*๋ฅผ ๋ฐํํ์ฌ ์ด๋ป๊ฒ ์ํํ๋์ง ๋ฐฐ์ฐ๊ฒ ๋ ๊ฒ์ ๋๋ค.
๊ฐ ๋จ๊ณ๋ฅผ ์์ธํ ์ดํด๋ณด๊ฒ ์ต๋๋ค.
์ปดํฌ๋ํธ ๋ด์์ Effect๋ฅผ ์ ์ธํ๋ ค๋ฉด, React์์ useEffect ํ
์ import ํ์ธ์.
import { useEffect } from 'react';๊ทธ๋ฐ ๋ค์, ์ปดํฌ๋ํธ์ ์ต์์ ๋ ๋ฒจ์์ ํธ์ถํ๊ณ Effect ๋ด๋ถ์ ์ฝ๋๋ฅผ ๋ฃ์ผ์ธ์.
function MyComponent() {
useEffect(() => {
// ์ด๊ณณ์ ์ฝ๋๋ *๋ชจ๋ * ๋ ๋๋ง ํ์ ์คํ๋ฉ๋๋ค
});
return <div />;
}์ปดํฌ๋ํธ๊ฐ ๋ ๋๋ง ๋ ๋๋ง๋ค React๋ ํ๋ฉด์ ์
๋ฐ์ดํธํ ๋ค์ useEffect ๋ด๋ถ์ ์ฝ๋๋ฅผ ์คํํฉ๋๋ค. ๋ค์ ๋งํด, useEffect๋ ํ๋ฉด์ ๋ ๋๋ง์ด ๋ฐ์๋ ๋๊น์ง ์ฝ๋ ์คํ์ "์ง์ฐ"์ํต๋๋ค.
์ด์ ์ธ๋ถ ์์คํ
๊ณผ ๋๊ธฐํํ๊ธฐ ์ํด ์ด๋ป๊ฒ Effect๋ฅผ ์ฌ์ฉํ ์ ์๋์ง ์์๋ณด๊ฒ ์ต๋๋ค. <VideoPlayer>๋ผ๋ React ์ปดํฌ๋ํธ๋ฅผ ์ดํด๋ณด๊ฒ ์ต๋๋ค. ์ด ์ปดํฌ๋ํธ๋ฅผ isPlaying์ด๋ผ๋ props๋ฅผ ํตํด ์ฌ์ ์ค์ธ์ง ์ผ์ ์ ์ง ์ํ์ธ์ง ์ ์ดํ๋ ๊ฒ์ด ์ข์ ๋ณด์ด๋ค์.
<VideoPlayer isPlaying={isPlaying} />;์ปค์คํ
VideoPlayer ์ปดํฌ๋ํธ๋ ๋ด์ฅ ๋ธ๋ผ์ฐ์ <video> ํ๊ทธ๋ฅผ ๋ ๋๋ง ํฉ๋๋ค.
function VideoPlayer({ src, isPlaying }) {
// TODO: isPlaying์ ํ์ฉํ์ฌ ๋ฌด์ธ๊ฐ ์ํํ๊ธฐ
return <video src={src} />;
}๊ทธ๋ฌ๋ <video> ํ๊ทธ์๋ isPlaying prop์ด ์์ต๋๋ค. ์ด๋ฅผ ์ ์ดํ๋ ์ ์ผํ ๋ฐฉ๋ฒ์ DOM ์์์์ ์๋์ผ๋ก play() ๋ฐ pause() ๋ฉ์๋๋ฅผ ํธ์ถํ๋ ๊ฒ์
๋๋ค. isPlaying prop์ ๊ฐ(ํ์ฌ ๋น๋์ค๊ฐ ์ฌ์ ์ค์ธ์ง ์ฌ๋ถ)์ play() ๋ฐ pause()์ ๊ฐ์ ํธ์ถ๊ณผ ๋๊ธฐํํด์ผ ํฉ๋๋ค.
๋จผ์ <video> DOM ๋
ธ๋์ ref๋ฅผ ๊ฐ์ ธ์์ผ ํฉ๋๋ค.
play() ๋๋ pause()๋ฅผ ๋ ๋๋ง ์ค์ ํธ์ถํ๋ ค๊ณ ์๋ํ ์ ์๊ฒ ์ง๋ง, ์ด๋ ์ฌ๋ฐ๋ฅธ ์ ๊ทผ์ด ์๋๋๋ค.
import { useState, useRef, useEffect } from 'react';
function VideoPlayer({ src, isPlaying }) {
const ref = useRef(null);
if (isPlaying) {
ref.current.play(); // ๋ ๋๋ง ์ค์ ์ด๋ฅผ ํธ์ถํ๋ ๊ฒ์ด ํ์ฉ๋์ง ์์ต๋๋ค.
} else {
ref.current.pause(); // ์ญ์ ์ด๋ ๊ฒ ํธ์ถํ๋ฉด ๋ฐ๋ก ์์ ํธ์ถ๊ณผ ์ถฉ๋์ด ๋ฐ์ํฉ๋๋ค.
}
return <video ref={ref} src={src} loop playsInline />;
}
export default function App() {
const [isPlaying, setIsPlaying] = useState(false);
return (
<>
<button onClick={() => setIsPlaying(!isPlaying)}>
{isPlaying ? '์ผ์์ ์ง' : '์ฌ์'}
</button>
<VideoPlayer
isPlaying={isPlaying}
src="https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4"
/>
</>
);
}button { display: block; margin-bottom: 20px; }
video { width: 250px; }์ด ์ฝ๋๊ฐ ์ฌ๋ฐ๋ฅด์ง ์์ ์ด์ ๋ ๋ ๋๋ง ์ค์ DOM ๋ ธ๋๋ฅผ ์กฐ์ํ๋ ค๊ณ ์๋ํ๊ธฐ ๋๋ฌธ์ ๋๋ค. React์์๋ ๋ ๋๋ง์ด JSX์ ์์ํ ๊ณ์ฐ์ด์ด์ผ ํ๋ฉฐ, DOM ์์ ๊ณผ ๊ฐ์ ๋ถ์ ํจ๊ณผ๋ฅผ ํฌํจํด์๋ ์๋ฉ๋๋ค.
๊ฒ๋ค๊ฐ, ์ฒ์์ผ๋ก VideoPlayer๊ฐ ํธ์ถ๋ ๋ ํด๋น DOM์ด ์์ง ์กด์ฌํ์ง ์์ต๋๋ค! React๋ ์ปดํฌ๋ํธ๊ฐ JSX๋ฅผ ๋ฐํํ ๋๊น์ง ์ด๋ค DOM์ ์์ฑํ ์ง ๋ชจ๋ฅด๊ธฐ ๋๋ฌธ์ play() ๋๋ pause()๋ฅผ ํธ์ถํ DOM ๋
ธ๋๊ฐ ์์ง ์์ต๋๋ค.
ํด๊ฒฐ์ฑ
์ ๋ถ์ ํจ๊ณผ๋ฅผ ๋ ๋๋ง ์ฐ์ฐ์์ ๋ถ๋ฆฌํ๊ธฐ ์ํด useEffect๋ก ๊ฐ์ธ๋ ๊ฒ์
๋๋ค.
import { useEffect, useRef } from 'react';
function VideoPlayer({ src, isPlaying }) {
const ref = useRef(null);
useEffect(() => {
if (isPlaying) {
ref.current.play();
} else {
ref.current.pause();
}
});
return <video ref={ref} src={src} loop playsInline />;
}DOM ์ ๋ฐ์ดํธ๋ฅผ Effect๋ก ๊ฐ์ธ๋ฉด React๊ฐ ํ๋ฉด์ ์ ๋ฐ์ดํธํ ๋ค์์ Effect๊ฐ ์คํ๋ฉ๋๋ค.
VideoPlayer ์ปดํฌ๋ํธ๊ฐ ๋ ๋๋ง ๋ ๋(์ฒ์ ํธ์ถํ๊ฑฐ๋ ๋ค์ ๋ ๋๋ง ํ ๋) ๋ช ๊ฐ์ง ์ผ์ด ๋ฐ์ํฉ๋๋ค. ๋จผ์ React๋ ํ๋ฉด์ ์
๋ฐ์ดํธํ์ฌ <video> ํ๊ทธ๊ฐ ์ฌ๋ฐ๋ฅธ ์์ฑ๊ณผ ํจ๊ป DOM์ ์๋์ง ํ์ธํฉ๋๋ค. ๊ทธ๋ฐ ๋ค์ React๋ Effect๋ฅผ ์คํํฉ๋๋ค. ๋ง์ง๋ง์ผ๋ก Effect์์๋ isPlaying ๊ฐ์ ๋ฐ๋ผ play() ๋๋ pause()๋ฅผ ํธ์ถํฉ๋๋ค.
"์ฌ์" ๋๋ "์ผ์ ์ ์ง"๋ฅผ ์ฌ๋ฌ ๋ฒ ๋๋ฌ๋ณด๊ณ ๋น๋์ค ํ๋ ์ด์ด๊ฐ isPlaying ๊ฐ๊ณผ ๋๊ธฐํ๋๋์ง ํ์ธํด ๋ณด์ธ์.
import { useState, useRef, useEffect } from 'react';
function VideoPlayer({ src, isPlaying }) {
const ref = useRef(null);
useEffect(() => {
if (isPlaying) {
ref.current.play();
} else {
ref.current.pause();
}
});
return <video ref={ref} src={src} loop playsInline />;
}
export default function App() {
const [isPlaying, setIsPlaying] = useState(false);
return (
<>
<button onClick={() => setIsPlaying(!isPlaying)}>
{isPlaying ? '์ผ์ ์ ์ง' : '์ฌ์'}
</button>
<VideoPlayer
isPlaying={isPlaying}
src="https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4"
/>
</>
);
}button { display: block; margin-bottom: 20px; }
video { width: 250px; }์ด ์์์์ React ์ํ์ ๋๊ธฐํ๋ "์ธ๋ถ ์์คํ "์ ๋ธ๋ผ์ฐ์ ๋ฏธ๋์ด API์์ต๋๋ค. ์ด์ ๋น์ทํ ์ ๊ทผ ๋ฐฉ์์ผ๋ก React๊ฐ ์๋ ๋ ๊ฑฐ์ ์ฝ๋(์: jQuery ํ๋ฌ๊ทธ์ธ)๋ฅผ ์ ์ธ์ ์ธ React ์ปดํฌ๋ํธ๋ก ๊ฐ์ธ๋ ๋ฐ์๋ ์ฌ์ฉํ ์ ์์ต๋๋ค.
์ค์ ๋ก ๋น๋์ค ํ๋ ์ด์ด๋ฅผ ์ ์ดํ๋ ๊ฒ์ ํจ์ฌ ๋ณต์กํฉ๋๋ค. play()๋ฅผ ํธ์ถํ๋ ๊ฒ์ด ์คํจํ ์ ์์ผ๋ฉฐ, ์ฌ์ฉ์๋ ์ปดํฌ๋ํธ์ UI๊ฐ ์๋ ๋ธ๋ผ์ฐ์ ๋ด์ฅ ์ปจํธ๋กค์ ์ฌ์ฉํ์ฌ ๋์์์ ์ฌ์ ๋๋ ์ผ์ ์ ์งํ ์ ์์ต๋๋ค. ์ด ์์๋ ๋งค์ฐ ๋จ์ํ๋์๊ณ ๋ถ์์ ํ ๊ฒ์์ ์ ์ํด์ฃผ์ธ์.
๊ธฐ๋ณธ์ ์ผ๋ก, Effect๋ ๋ชจ๋ ๋ ๋๋ง ํ์ ์คํ๋ฉ๋๋ค. ์ด๋ฌํ ์ด์ ๋ก ๋ค์๊ณผ ๊ฐ์ ์ฝ๋๋ ๋ฌดํ ๋ฃจํ๋ฅผ ๋ง๋ค์ด๋ผ ๊ฒ์ ๋๋ค.
const [count, setCount] = useState(0);
useEffect(() => {
setCount(count + 1);
});Effect๋ ๋ ๋๋ง์ ๊ฒฐ๊ณผ๋ก ์คํ๋ฉ๋๋ค. state๋ฅผ ์ค์ ํ๋ฉด ๋ ๋๋ง์ด ํธ๋ฆฌ๊ฑฐ๋ฉ๋๋ค. Effect ์์์ ์ฆ์ ์ํ๋ฅผ ์ค์ ํ๋ ๊ฒ์ ๊ธฐ๊ณ์ ์ ์ ํ๋ฌ๊ทธ๋ฅผ ๊ธฐ๊ณ ๊ทธ ์์ฒด์ ์ฐ๊ฒฐํ๋ ๊ฒ๊ณผ ๋น์ทํฉ๋๋ค. Effect๊ฐ ์คํ๋๊ณ ์ํ๊ฐ ์ค์ ๋๋ฉด ์ฌ๋ ๋๋ง์ด ๋ฐ์ํ๊ณ , Effect๊ฐ ๋ค์ ์คํ๋๊ณ ์ํ๊ฐ ์ค์ ๋๋ฉด ๋ ๋ค๋ฅธ ์ฌ๋ ๋๋ง์ด ๋ฐ์ํ๋ฉฐ, ์ด๋ฐ ์์ผ๋ก ๊ณ์๋ฉ๋๋ค.
Effect๋ ์ผ๋ฐ์ ์ผ๋ก ์ปดํฌ๋ํธ๋ฅผ ์ธ๋ถ ์์คํ ๊ณผ ๋๊ธฐํํ๋ ๋ฐ ์ฌ์ฉ๋ฉ๋๋ค. ์ธ๋ถ ์์คํ ์ด ์๊ณ ๋ค๋ฅธ ์ํ์ ๊ธฐ๋ฐํ์ฌ ์ํ๋ฅผ ์กฐ์ ํ๋ ค๋ ๊ฒฝ์ฐ์๋ Effect๊ฐ ํ์ํ์ง ์์ ์ ์์ต๋๋ค.
๊ธฐ๋ณธ์ ์ผ๋ก, Effect๋ ๋ชจ๋ ๋ ๋๋ง ํ์ ์คํ๋ฉ๋๋ค. ์ด๋ ์ข ์ข ์ํ๋ ๋์์ด ์๋ ์ ์์ต๋๋ค:
- ๋๋๋ก ๋๋ฆด ์ ์์ต๋๋ค. ์ธ๋ถ ์์คํ ๊ณผ ๋๊ธฐํํ๋ ๊ฒ์ด ํญ์ ์ฆ์ ์ด๋ฃจ์ด์ง์ง ์๊ธฐ ๋๋ฌธ์ ํ์ํ์ง ์์ ๊ฒฝ์ฐ์๋ ์คํ์ ๊ฑด๋๋ฐ๊ณ ์ถ์ ์ ์์ต๋๋ค. ์๋ฅผ ๋ค์ด, ๋ชจ๋ ํค ์ ๋ ฅ๋ง๋ค ์ฑํ ์๋ฒ์ ๋ค์ ์ฐ๊ฒฐํ๊ธธ ์ํ์ง ์์ ๊ฒ์ ๋๋ค.
- ๋๋๋ก ์๋ชป๋ ์ ์์ต๋๋ค. ์๋ฅผ ๋ค์ด, ๋ชจ๋ ํค ์ ๋ ฅ๋ง๋ค ์ปดํฌ๋ํธ fade-in ์ ๋๋ฉ์ด์ ์ ํธ๋ฆฌ๊ฑฐํ๊ธธ ์ํ์ง ์์ ๊ฒ์ ๋๋ค. ์ ๋๋ฉ์ด์ ์ ์ปดํฌ๋ํธ๊ฐ ์ฒ์ ๋ํ๋ ๋์๋ง ํ ๋ฒ ์คํ๋์ด์ผ ํฉ๋๋ค.
์ด ๋ฌธ์ ๋ฅผ ์ค๋ช
ํ๊ธฐ ์ํด ์ด์ ์์์ ๋ช ๊ฐ์ง console.log ํธ์ถ๊ณผ ๋ถ๋ชจ ์ปดํฌ๋ํธ์ ์ํ๋ฅผ ์
๋ฐ์ดํธํ๋ ํ
์คํธ ์
๋ ฅ์ ์ถ๊ฐํ ์์๋ฅผ ์ดํด๋ณด๊ฒ ์ต๋๋ค. ์
๋ ฅํ ๋ Effect๊ฐ ๋ค์ ์คํ๋๋ ๊ฒ์ ์ฃผ๋ชฉํ์ธ์.
import { useState, useRef, useEffect } from 'react';
function VideoPlayer({ src, isPlaying }) {
const ref = useRef(null);
useEffect(() => {
if (isPlaying) {
console.log('video.play() ํธ์ถ');
ref.current.play();
} else {
console.log('video.pause() ํธ์ถ');
ref.current.pause();
}
});
return <video ref={ref} src={src} loop playsInline />;
}
export default function App() {
const [isPlaying, setIsPlaying] = useState(false);
const [text, setText] = useState('');
return (
<>
<input value={text} onChange={e => setText(e.target.value)} />
<button onClick={() => setIsPlaying(!isPlaying)}>
{isPlaying ? '์ผ์ ์ ์ง' : '์ฌ์'}
</button>
<VideoPlayer
isPlaying={isPlaying}
src="https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4"
/>
</>
);
}input, button { display: block; margin-bottom: 20px; }
video { width: 250px; }React์๊ฒ Effect๋ฅผ ๋ถํ์ํ๊ฒ ๋ค์ ์คํํ์ง ์๋๋ก ์ง์ํ๋ ค๋ฉด useEffect ํธ์ถ์ ๋ ๋ฒ์งธ ์ธ์๋ก ์์กด์ฑ(dependencies) ๋ฐฐ์ด์ ์ง์ ํ์ธ์. ๋จผ์ ์์ ์์์ ๋น [] ๋ฐฐ์ด์ 14๋ฒ์งธ ์ค์ ์ถ๊ฐํ๋ฉด ๋ฉ๋๋ค.
useEffect(() => {
// ...
}, []);'isPlaying'์ ๋ํ ์์กด์ฑ์ด ๋๋ฝ๋์๋ค๋ ์ค๋ฅ๊ฐ ํ์๋ ๊ฒ์
๋๋ค.
import { useState, useRef, useEffect } from 'react';
function VideoPlayer({ src, isPlaying }) {
const ref = useRef(null);
useEffect(() => {
if (isPlaying) {
console.log('video.play() ํธ์ถ');
ref.current.play();
} else {
console.log('video.pause() ํธ์ถ');
ref.current.pause();
}
}, []); // ์ด ์ฝ๋๋ ์๋ฌ๋ฅผ ์ ๋ฐํฉ๋๋ค
return <video ref={ref} src={src} loop playsInline />;
}
export default function App() {
const [isPlaying, setIsPlaying] = useState(false);
const [text, setText] = useState('');
return (
<>
<input value={text} onChange={e => setText(e.target.value)} />
<button onClick={() => setIsPlaying(!isPlaying)}>
{isPlaying ? '์ผ์ ์ ์ง' : '์ฌ์'}
</button>
<VideoPlayer
isPlaying={isPlaying}
src="https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4"
/>
</>
);
}input, button { display: block; margin-bottom: 20px; }
video { width: 250px; }๋ฌธ์ ๋ Effect ๋ด๋ถ์ ์ฝ๋๊ฐ ์ด๋ค ์์
์ ์ํํ ์ง ๊ฒฐ์ ํ๊ธฐ ์ํด isPlaying prop์ ์์กดํ์ง๋ง ์ด ์์กด์ฑ์ด ๋ช
์์ ์ผ๋ก ์ ์ธ๋์ง ์์๋ค๋ ๊ฒ์
๋๋ค. ์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๋ ค๋ฉด ์์กด์ฑ ๋ฐฐ์ด์ isPlaying์ ์ถ๊ฐํ์ธ์.
useEffect(() => {
if (isPlaying) { // ์ฌ๊ธฐ์ ์ฌ์ฉํ๋๊น...
// ...
} else {
// ...
}
}, [isPlaying]); // ...์ฌ๊ธฐ์ ์ ์ธ๋์ด์ผ๊ฒ ๋ค!์ด์ ๋ชจ๋ ์์กด์ฑ์ด ์์กด์ฑ ๋ฐฐ์ด ์์ ์ ์ธ๋์ด ์ค๋ฅ๊ฐ ์์ ๊ฒ์
๋๋ค. ์์กด์ฑ ๋ฐฐ์ด๋ก [isPlaying]์ ์ง์ ํ๋ฉด React์๊ฒ ์ด์ ๋ ๋๋ง ์ค์ isPlaying์ด ์ด์ ๊ณผ ๋์ผํ๋ค๋ฉด Effect๋ฅผ ๋ค์ ์คํํ์ง ์๋๋ก ํด์ผ ํ๋ค๊ณ ์๋ ค์ค๋๋ค. ์ด ๋ณ๊ฒฝ์ผ๋ก ์
๋ ฅ๋์ ์
๋ ฅ์ ์
๋ ฅํ๋ฉด Effect๊ฐ ๋ค์ ์คํ๋์ง ์๊ณ , ์ฌ์/์ผ์ ์ ์ง ๋ฒํผ์ ๋๋ฅด๋ฉด Effect๊ฐ ์คํ๋ฉ๋๋ค.
import { useState, useRef, useEffect } from 'react';
function VideoPlayer({ src, isPlaying }) {
const ref = useRef(null);
useEffect(() => {
if (isPlaying) {
console.log('video.play() ํธ์ถ');
ref.current.play();
} else {
console.log('video.pause() ํธ์ถ');
ref.current.pause();
}
}, [isPlaying]);
return <video ref={ref} src={src} loop playsInline />;
}
export default function App() {
const [isPlaying, setIsPlaying] = useState(false);
const [text, setText] = useState('');
return (
<>
<input value={text} onChange={e => setText(e.target.value)} />
<button onClick={() => setIsPlaying(!isPlaying)}>
{isPlaying ? '์ผ์ ์ ์ง' : '์ฌ์'}
</button>
<VideoPlayer
isPlaying={isPlaying}
src="https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4"
/>
</>
);
}input, button { display: block; margin-bottom: 20px; }
video { width: 250px; }์์กด์ฑ ๋ฐฐ์ด์๋ ์ฌ๋ฌ ๊ฐ์ ์ข
์์ฑ์ ํฌํจํ ์ ์์ต๋๋ค. React๋ ์ง์ ํ ๋ชจ๋ ์ข
์์ฑ์ด ์ด์ ๋ ๋๋ง์ ๊ทธ๊ฒ๊ณผ ์ ํํ ๋์ผํ ๊ฐ์ ๊ฐ์ง ๊ฒฝ์ฐ์๋ง Effect๋ฅผ ๋ค์ ์คํํ์ง ์์ต๋๋ค. React๋ Object.is ๋น๊ต๋ฅผ ์ฌ์ฉํ์ฌ ์ข
์์ฑ ๊ฐ์ ๋น๊ตํฉ๋๋ค. ์์ธํ ๋ด์ฉ์ useEffect ์ฐธ์กฐ ๋ฌธ์๋ฅผ ์ฐธ์กฐํ์ธ์.
์์กด์ฑ์ "์ ํ"ํ ์ ์๋ค๋ ์ ์ ์ ์ํ์ธ์. ์์กด์ฑ ๋ฐฐ์ด์ ์ง์ ํ ์ข ์์ฑ์ด Effect ๋ด๋ถ์ ์ฝ๋๋ฅผ ๊ธฐ๋ฐ์ผ๋ก React๊ฐ ๊ธฐ๋ํ๋ ๊ฒ๊ณผ ์ผ์นํ์ง ์์ผ๋ฉด ๋ฆฐํธ ์๋ฌ๊ฐ ๋ฐ์ํฉ๋๋ค. ์ด๋ฅผ ํตํด ์ฝ๋ ๋ด์ ๋ง์ ๋ฒ๊ทธ๋ฅผ ์ก์ ์ ์์ต๋๋ค. ์ฝ๋๊ฐ ๋ค์ ์คํ๋๊ธธ ์ํ์ง ์๋ ๊ฒฝ์ฐ, Effect ๋ด๋ถ๋ฅผ ์์ ํ์ฌ ๊ทธ ์ข ์์ฑ์ด "ํ์"ํ์ง ์๋๋ก ๋ง๋์ธ์.
์์กด์ฑ ๋ฐฐ์ด์ด ์๋ ๊ฒฝ์ฐ์ ๋น [] ์์กด์ฑ ๋ฐฐ์ด์ด ์๋ ๊ฒฝ์ฐ์ ๋์์ด ๋ค๋ฆ
๋๋ค.
useEffect(() => {
// ๋ชจ๋ ๋ ๋๋ง ํ์ ์คํ๋ฉ๋๋ค
});
useEffect(() => {
// ๋ง์ดํธ๋ ๋๋ง ์คํ๋ฉ๋๋ค (์ปดํฌ๋ํธ๊ฐ ๋ํ๋ ๋)
}, []);
useEffect(() => {
// ๋ง์ดํธ๋ ๋ ์คํ๋๋ฉฐ, *๋ํ* ๋ ๋๋ง ์ดํ์ a ๋๋ b ์ค ํ๋๋ผ๋ ๋ณ๊ฒฝ๋ ๊ฒฝ์ฐ์๋ ์คํ๋ฉ๋๋ค
}, [a, b]);๋ค์ ๋จ๊ณ์์ "๋ง์ดํธ(mount)"๊ฐ ๋ฌด์์ ์๋ฏธํ๋์ง ์์ธํ ์ดํด๋ณด๊ฒ ์ต๋๋ค.
์ ref๋ ์์กด์ฑ ๋ฐฐ์ด์์ ์๋ตํด๋ ๋๋์? {/why-was-the-ref-omitted-from-the-dependency-array/}
์ด Effect๋ ref์ isPlaying์ ๋ชจ๋ ์ฌ์ฉํ์ง๋ง, ์์กด์ฑ ๋ฐฐ์ด ์์ ์ ์ธ๋ ๊ฒ์ isPlaying ๋ฟ์
๋๋ค.
function VideoPlayer({ src, isPlaying }) {
const ref = useRef(null);
useEffect(() => {
if (isPlaying) {
ref.current.play();
} else {
ref.current.pause();
}
}, [isPlaying]);์ด๊ฒ์ ref ๊ฐ์ฒด๊ฐ *์์ ๋ ์๋ณ์ฑ(stable identity)*์ ๊ฐ์ง๊ธฐ ๋๋ฌธ์
๋๋ค. React๋ ๋์ผํ useRef ํธ์ถ์์ ํญ์ ๊ฐ์ ๊ฐ์ฒด๋ฅผ ์ป์ ์ ์์์ ๋ณด์ฅํฉ๋๋ค. ์ด ๊ฐ์ฒด๋ ์ ๋ ๋ณ๊ฒฝ๋์ง ์๊ธฐ ๋๋ฌธ์ ์์ฒด์ ์ผ๋ก Effect๋ฅผ ๋ค์ ์คํ์ํค์ง ์์ต๋๋ค. ๋ฐ๋ผ์ ref๋ ์์กด์ฑ ๋ฐฐ์ด์ ํฌํจํ๋ ํฌํจํ์ง ์๋ ์๊ด์์ต๋๋ค. ํฌํจํด๋ ๋ฌธ์ ์์ต๋๋ค.
function VideoPlayer({ src, isPlaying }) {
const ref = useRef(null);
useEffect(() => {
if (isPlaying) {
ref.current.play();
} else {
ref.current.pause();
}
}, [isPlaying, ref]);useState๋ก ๋ฐํ๋๋ set ํจ์๋ค๋ ์์ ๋ ์๋ณ์ฑ์ ๊ฐ์ง๊ธฐ ๋๋ฌธ์, ์ข
์ข
์ด๋ฌํ ํจ์๋ค๋ ์์กด์ฑ์์ ์๋ต๋๋ ๊ฒ์ ๋ณผ ์ ์์ต๋๋ค. ๋ฆฐํฐ๊ฐ ์์กด์ฑ์ ์๋ตํด๋ ์ค๋ฅ๋ฅผ ํ์ํ์ง ์๋๋ค๋ฉด ๊ทธ๋ ๊ฒ ํด๋ ์์ ํฉ๋๋ค.
์์ ๋ ์๋ณ์ฑ์ ๊ฐ์ง ์์กด์ฑ์ ์๋ตํ๋ ๊ฒ์ ๋ฆฐํฐ๊ฐ ํด๋น ๊ฐ์ฒด๊ฐ ์์ ์ ์์ "์ ์" ์๋ ๊ฒฝ์ฐ์๋ง ์๋ํฉ๋๋ค. ์๋ฅผ ๋ค์ด, ref๊ฐ ๋ถ๋ชจ ์ปดํฌ๋ํธ์์ ์ ๋ฌ๋์๋ค๋ฉด, ์์กด์ฑ ๋ฐฐ์ด์ ๋ช
์ํด์ผ ํฉ๋๋ค. ์ด๊ฒ์ ์ข์ ์ ๊ทผ ๋ฐฉ์์
๋๋ค. ์๋ํ๋ฉด ๋ถ๋ชจ ์ปดํฌ๋ํธ๊ฐ ํญ์ ๋์ผํ ref๋ฅผ ์ ๋ฌํ๋์ง ๋๋ ์ฌ๋ฌ ref ์ค ํ๋๋ฅผ ์กฐ๊ฑด๋ถ๋ก ์ ๋ฌํ๋์ง ์ ์ ์๊ธฐ ๋๋ฌธ์
๋๋ค. ๋ฐ๋ผ์ ๋น์ ์ Effect๋ ์ ๋ฌ๋๋ ref์ ๋ฐ๋ผ ๋ฌ๋ผ์ง ๊ฒ์
๋๋ค.
๋ค๋ฅธ ์์๋ฅผ ๊ณ ๋ คํด ๋ณด๊ฒ ์ต๋๋ค. ์ฌ์ฉ์์๊ฒ ํ์๋ ๋ ์ฑํ
์๋ฒ์ ์ฐ๊ฒฐํด์ผ ํ๋ ChatRoom ์ปดํฌ๋ํธ๋ฅผ ์์ฑ ์ค์
๋๋ค. createConnection() API๊ฐ ์ฃผ์ด์ง๋ฉฐ, ์ด API๋ connect() ๋ฐ disconnect() ๋ฉ์๋๋ฅผ ๊ฐ์ง ๊ฐ์ฒด๋ฅผ ๋ฐํํฉ๋๋ค. ์ฌ์ฉ์์๊ฒ ํ์๋๋ ๋์ ์ปดํฌ๋ํธ๊ฐ ์ฑํ
์๋ฒ์์ ์ฐ๊ฒฐ์ ์ ์งํ๋ ค๋ฉด ์ด๋ป๊ฒ ํด์ผ ํ ๊น์?
๋จผ์ Effect๋ฅผ ์์ฑํด ๋ณด๊ฒ ์ต๋๋ค.
useEffect(() => {
const connection = createConnection();
connection.connect();
});๋งค๋ฒ ์ฌ๋ ๋๋ง ํ์ ์ฑํ ์๋ฒ์ ์ฐ๊ฒฐํ๋ ๊ฒ์ ๋๋ฆฌ๋ฏ๋ก ์์กด์ฑ ๋ฐฐ์ด์ ์ถ๊ฐํฉ๋๋ค.
useEffect(() => {
const connection = createConnection();
connection.connect();
}, []);Effect ๋ด๋ถ์ ์ฝ๋๋ ์ด๋ ํ props๋ ์ํ๋ ์ฌ์ฉํ์ง ์์ผ๋ฏ๋ก, ์์กด์ฑ ๋ฐฐ์ด์ [] (๋น ๋ฐฐ์ด)์
๋๋ค. ์ด๋ React์๊ฒ ์ด ์ฝ๋๋ฅผ ์ปดํฌ๋ํธ๊ฐ "๋ง์ดํธ"๋ ๋๋ง ์คํํ๋๋ก ์๋ ค์ค๋๋ค. ์ฆ, ํ๋ฉด์ ์ฒ์์ผ๋ก ๋ํ๋ ๋์๋ง ์คํ๋๊ฒ ๋ฉ๋๋ค.
์ด ์ฝ๋๋ฅผ ์คํํด ๋ณด๊ฒ ์ต๋๋ค.
import { useEffect } from 'react';
import { createConnection } from './chat.js';
export default function ChatRoom() {
useEffect(() => {
const connection = createConnection();
connection.connect();
}, []);
return <h1>์ฑํ
์ ์ค์ ๊ฑธ ํ์ํฉ๋๋ค!</h1>;
}export function createConnection() {
// ์ค์ ๊ตฌํ์ ์ ๋ง๋ก ์ฑํ
์๋ฒ์ ์ฐ๊ฒฐํ๋ ๊ฒ์ด ๋์ด์ผ ํฉ๋๋ค.
return {
connect() {
console.log('โ
์ฐ๊ฒฐ ์ค...');
},
disconnect() {
console.log('โ ์ฐ๊ฒฐ์ด ๋๊ฒผ์ต๋๋ค.');
}
};
}input { display: block; margin-bottom: 20px; }์ด Effect๋ ๋ง์ดํธ๋ ๋๋ง ์คํ๋๋ฏ๋ก ์ฝ์์ "โ ์ฐ๊ฒฐ ์ค..."์ด ํ ๋ฒ ์ถ๋ ฅ๋ ๊ฒ์ผ๋ก ์์ํ ์ ์์ต๋๋ค. ๊ทธ๋ฌ๋ ์ฝ์์ ํ์ธํด ๋ณด๋ฉด "โ ์ฐ๊ฒฐ ์ค..."์ด ๋ ๋ฒ ์ถ๋ ฅ๋ฉ๋๋ค. ์ ๊ทธ๋ด๊น์?
ChatRoom ์ปดํฌ๋ํธ๊ฐ ์ฌ๋ฌ ํ๋ฉด์ผ๋ก ๊ตฌ์ฑ๋ ํฐ ์ฑ์ ์ผ๋ถ๋ผ๊ณ ๊ฐ์ ํด ๋ณด๊ฒ ์ต๋๋ค. ์ฌ์ฉ์๊ฐ ChatRoom ํ์ด์ง์์ ์ฌ์ ์ ์์ํฉ๋๋ค. ์ปดํฌ๋ํธ๊ฐ ๋ง์ดํธ๋๊ณ connection.connect()๋ฅผ ํธ์ถํฉ๋๋ค. ๊ทธ๋ฐ ๋ค์ ์ฌ์ฉ์๊ฐ ๋ค๋ฅธ ํ๋ฉด์ผ๋ก ์ด๋ํ๋ค๊ณ ์์ํด๋ณด์ธ์. ์๋ฅผ ๋ค์ด, ์ค์ ํ์ด์ง๋ก ์ด๋ํ ์ ์์ต๋๋ค. ChatRoom ์ปดํฌ๋ํธ๊ฐ ๋ง์ดํธ ํด์ ๋ฉ๋๋ค. ๋ง์ง๋ง์ผ๋ก ์ฌ์ฉ์๊ฐ ๋ค๋ก ๊ฐ๊ธฐ ๋ฒํผ์ ํด๋ฆญํ๊ณ ChatRoom์ด ๋ค์ ๋ง์ดํธ๋ฉ๋๋ค. ์ด๋ ๊ฒ ๋๋ฉด ๋ ๋ฒ์งธ ์ฐ๊ฒฐ์ด ์ค์ ๋์ง๋ง ์ฒซ ๋ฒ์งธ ์ฐ๊ฒฐ์ ์ข
๋ฃ๋์ง ์์์ต๋๋ค! ์ฌ์ฉ์๊ฐ ์ฑ์ ํ์ํ๋ ๋์ ์ฐ๊ฒฐ์ ์ข
๋ฃ๋์ง ์๊ณ ๊ณ์ ์์ผ ๊ฒ์
๋๋ค.
์ด์ ๊ฐ์ ๋ฒ๊ทธ๋ ์ฑ์ ์ด๊ณณ์ ๊ณณ์ ์๋์ผ๋ก ํ ์คํธํด๋ณด์ง ์์ผ๋ฉด ๋์น๊ธฐ ์ฝ์ต๋๋ค. ์ด๋ฌํ ๋ฌธ์ ๋ฅผ ๋น ๋ฅด๊ฒ ํ์ ํ ์ ์๋๋ก React๋ ๊ฐ๋ฐ ๋ชจ๋์์ ์ด๊ธฐ ๋ง์ดํธ ํ ๋ชจ๋ ์ปดํฌ๋ํธ๋ฅผ ํ ๋ฒ ๋ค์ ๋ง์ดํธํฉ๋๋ค.
"โ ์ฐ๊ฒฐ ์ค..." ๋ก๊ทธ๊ฐ ๋ ๋ฒ ์ถ๋ ฅ๋๋ ๊ฒ์ ๋ณด๋ฉด ๊ฒฐ๊ตญ ๋ฌด์์ด ๋ฌธ์ ์ธ์ง ์ ์ ์์ต๋๋ค. ์ปดํฌ๋ํธ๊ฐ ๋ง์ดํธ ํด์ ๋ ๋ ์ฐ๊ฒฐ์ ๋ซ์ง ์๋ ๋ฌธ์ ๊ฐ ๋ฐ๋ก ๊ทธ๊ฒ์ด์ฃ .
์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๋ ค๋ฉด Effect์์ ํด๋ฆฐ์ ํจ์๋ฅผ ๋ฐํํ๋ฉด ๋ฉ๋๋ค.
useEffect(() => {
const connection = createConnection();
connection.connect();
return () => {
connection.disconnect();
};
}, []);React๋ Effect๊ฐ ๋ค์ ์คํ๋๊ธฐ ์ ๋ง๋ค ํด๋ฆฐ์ ํจ์๋ฅผ ํธ์ถํ๊ณ , ์ปดํฌ๋ํธ๊ฐ ๋ง์ดํธ ํด์ (์ ๊ฑฐ)๋ ๋์๋ ๋ง์ง๋ง์ผ๋ก ํธ์ถํฉ๋๋ค. ํด๋ฆฐ์ ํจ์๊ฐ ๊ตฌํ๋ ๊ฒฝ์ฐ ์ด๋ค ์ผ์ด ์ผ์ด๋๋์ง ์ดํด๋ณด๊ฒ ์ต๋๋ค.
import { useState, useEffect } from 'react';
import { createConnection } from './chat.js';
export default function ChatRoom() {
useEffect(() => {
const connection = createConnection();
connection.connect();
return () => connection.disconnect();
}, []);
return <h1>์ฑํ
์ ์ค์ ๊ฑธ ํ์ํฉ๋๋ค!</h1>;
}export function createConnection() {
// ์ค์ ๊ตฌํ์ ์ ๋ง๋ก ์ฑํ
์๋ฒ์ ์ฐ๊ฒฐํ๋ ๊ฒ์ด ๋์ด์ผ ํฉ๋๋ค.
return {
connect() {
console.log('โ
์ฐ๊ฒฐ ์ค...');
},
disconnect() {
console.log('โ ์ฐ๊ฒฐ ํด์ ๋จ');
}
};
}input { display: block; margin-bottom: 20px; }์ด์ ๊ฐ๋ฐ ๋ชจ๋์์ ์ธ ๊ฐ์ ์ฝ์ ๋ก๊ทธ๋ฅผ ํ์ธํ ์ ์์ต๋๋ค:
"โ ์ฐ๊ฒฐ ์ค...""โ ์ฐ๊ฒฐ ํด์ ๋จ""โ ์ฐ๊ฒฐ ์ค..."
์ด๊ฒ์ด ๊ฐ๋ฐ ๋ชจ๋์์ ์ฌ๋ฐ๋ฅธ ๋์์ ๋๋ค. ์ปดํฌ๋ํธ๋ฅผ ๋ค์ ๋ง์ดํธํจ์ผ๋ก์จ React๋ ์ฌ์ฉ์๊ฐ ๋ค๋ฅธ ๋ถ๋ถ์ ํ์ํ๊ณ ๋ค์ ๋์์๋ ์ฝ๋๊ฐ ๊นจ์ง์ง ์์ ๊ฒ์์ ํ์ธํฉ๋๋ค. ์ฐ๊ฒฐ์ ํด์ ํ๊ณ ๋ค์ ์ฐ๊ฒฐํ๋ ๊ฒ์ด ๋ฐ๋ก ์ผ์ด๋๋ ์ผ์ ๋๋ค! ํด๋ฆฐ์ ์ ์ ๊ตฌํํ๋ฉด Effect๋ฅผ ํ ๋ฒ ์คํํ๋ ๊ฒ๊ณผ ์คํ, ํด๋ฆฐ์ , ์ดํ ๋ค์ ์คํํ๋ ๊ฒ ์ฌ์ด์ ์ฌ์ฉ์์๊ฒ ๋ณด์ด๋ ์ฐจ์ด๊ฐ ์์ด์ผ ํฉ๋๋ค. ๊ฐ๋ฐ ์ค์๋ ์ฐ๊ฒฐ/ํด์ ํธ์ถ์ด ํ๋ ๋ ์๋๋ฐ, ์ด๋ React๊ฐ ๊ฐ๋ฐ ์ค์ ์ฝ๋๋ฅผ ๊ฒ์ฌํ์ฌ ๋ฒ๊ทธ๋ฅผ ์ฐพ๋ ๊ฒ์ ๋๋ค. ์ด๊ฒ์ ์ ์์ ์ธ ๋์์ ๋๋ค - ์ด๊ฒ์ ์์ ๋ ค๊ณ ํ์ง ๋ง์ธ์!
๋ฐฐํฌ ํ๊ฒฝ์์๋ "โ
์ฐ๊ฒฐ ์ค..."์ด ํ ๋ฒ๋ง ์ถ๋ ฅ๋ฉ๋๋ค. ์ปดํฌ๋ํธ๋ฅผ ๋ค์ ๋ง์ดํธํ๋ ๊ฒ์ ๊ฐ๋ฐ ์ค์๋ง ๋ฐ์ํ๋ฉฐ ํด๋ฆฐ์
์ด ํ์ํ Effect๋ฅผ ์ฐพ์์ฃผ๋ ๋ฐ ๋์์ ์ค๋๋ค. ๊ฐ๋ฐ ๋์์์ ๋ฒ์ด๋๋ ค๋ฉด Strict Mode๋ฅผ ๋๋ ๊ฒ๋ ๊ฐ๋ฅํ์ง๋ง, ์ผ๋ ๊ฒ์ ๊ถ์ฅํฉ๋๋ค. ์ด๋ ๊ฒ ํ๋ฉด ์์ ๊ฐ์ ๋ง์ ๋ฒ๊ทธ๋ฅผ ์ฐพ์ ์ ์์ต๋๋ค.
๊ฐ๋ฐ ์ค์ Effect๊ฐ ๋ ๋ฒ ์คํ๋๋ ๊ฒฝ์ฐ๋ฅผ ๋ค๋ฃจ๋ ๋ฐฉ๋ฒ {/how-to-handle-the-effect-firing-twice-in-development/}
React๋ ๋ง์ง๋ง ์์์ ๊ฐ์ ๋ฒ๊ทธ๋ฅผ ์ฐพ๊ธฐ ์ํด ๊ฐ๋ฐ ์ค์ ์ปดํฌ๋ํธ๋ฅผ ๋ช ์์ ์ผ๋ก ๋ค์ ๋ง์ดํธํฉ๋๋ค. "Effect๋ฅผ ํ ๋ฒ ์คํํ๋ ๋ฐฉ๋ฒ"์ด ์๋๋ผ "์ด๋ป๊ฒ Effect๊ฐ ๋ค์ ๋ง์ดํธ๋ ํ์๋ ์๋ํ๋๋ก ๊ณ ์น ๊ฒ์ธ๊ฐ"๋ผ๋ ๊ฒ์ด ์ณ์ ์ง๋ฌธ์ ๋๋ค.
์ผ๋ฐ์ ์ผ๋ก ์ ๋ต์ ํด๋ฆฐ์ ํจ์๋ฅผ ๊ตฌํํ๋ ๊ฒ์ ๋๋ค. ํด๋ฆฐ์ ํจ์๋ Effect๊ฐ ์ํํ๋ ์์ ์ ์ค๋จํ๊ฑฐ๋ ๋๋๋ฆฌ๋ ์ญํ ์ ํฉ๋๋ค. ๊ธฐ๋ณธ ์์น์ ์ฌ์ฉ์๊ฐ Effect๊ฐ ํ ๋ฒ ์คํ๋๋ ๊ฒ(๋ฐฐํฌ ํ๊ฒฝ๊ณผ ๊ฐ์ด)๊ณผ ์ค์ โ ํด๋ฆฐ์ โ ์ค์ ์์(๊ฐ๋ฐ ์ค์ ๋ณผ ์ ์๋ ๊ฒ) ๊ฐ์ ์ฐจ์ด๋ฅผ ๋๋ผ์ง ๋ชปํด์ผ ํฉ๋๋ค.
์์ฑํ ๋๋ถ๋ถ์ Effect๋ ์๋์ ์ผ๋ฐ์ ์ธ ํจํด ์ค ํ๋์ ํด๋น๋ ๊ฒ์ ๋๋ค.
Effect๊ฐ ๋ ๋ฒ ์คํ๋๋ ๊ฒ์ ๋ง๊ธฐ์ํด ref๋ฅผ ์ฌ์ฉํ์ง ๋ง์ธ์ {/dont-use-refs-to-prevent-effects-from-firing/}
Effect๊ฐ ๊ฐ๋ฐ ๋ชจ๋์์ ๋ ๋ฒ ์คํ๋๋ ๊ฒ์ ๋ง์ผ๋ ค๋ค ํํ ๋น ์ง๋ ํจ์ ์ ref๋ฅผ ์ฌ์ฉํด Effect๊ฐ ํ ๋ฒ๋ง ์คํ๋๋๋ก ํ๋ ๊ฒ์
๋๋ค. ์๋ฅผ ๋ค์ด ์์ ๋ฒ๊ทธ๋ฅผ useRef๋ฅผ ์ฌ์ฉํ์ฌ "์์ "ํ๋ ค๊ณ ํ ์๋ ์์ต๋๋ค:
const connectionRef = useRef(null);
useEffect(() => {
// ๐ฉ ๋ฒ๊ทธ๋ฅผ ์์ ํ์ง ์์ต๋๋ค!!!
if (!connectionRef.current) {
connectionRef.current = createConnection();
connectionRef.current.connect();
}
}, []);์ด๋ ๊ฒ ํ๋ฉด ๊ฐ๋ฐ ๋ชจ๋์์ "โ
์ฐ๊ฒฐ ์ค..."์ด ํ ๋ฒ๋ง ๋ณด์ด์ง๋ง ๋ฒ๊ทธ๊ฐ ์์ ๋ ๊ฑด ์๋๋๋ค.
When the user navigates away, the connection still isn't closed and when they navigate back, a new connection is created. As the user navigates across the app, the connections would keep piling up, the same as it would before the "fix".
๋ฒ๊ทธ๋ฅผ ์์ ํ๊ธฐ ์ํด์ Effect๋ฅผ ๋จ์ํ ํ ๋ฒ๋ง ์คํ๋๋๋ก ๋ง๋๋ ๊ฒ์ผ๋ก๋ ๋ถ์กฑํฉ๋๋ค. Effect๋ ์์ ์๋ ์์๊ฐ ์ฐ๊ฒฐ์ ํด๋ฆฐ์ ํ๊ฒ์ฒ๋ผ ๋ค์ ๋ง์ดํธ๋ ์ดํ์๋ ์ ๋๋ก ๋์ํด์ผ ํฉ๋๋ค.
์๋์ ์๋ ์ผ๋ฐ์ ์ธ ํจํด์ ๋ค๋ฃจ๋ ์์๋ฅผ ์ดํด๋ณด์ธ์.
๊ฐ๋์ฉ React๋ก ์์ฑ๋์ง ์์ UI ์์ ฏ์ ์ถ๊ฐํด์ผ ํ ๋๊ฐ ์์ต๋๋ค. ์๋ฅผ ๋ค์ด, ํ์ด์ง์ ์ง๋ ์ปดํฌ๋ํธ๋ฅผ ์ถ๊ฐํ๋ค๊ณ ๊ฐ์ ํด ๋ณด๊ฒ ์ต๋๋ค. ์ด ์ง๋ ์ปดํฌ๋ํธ์๋ setZoomLevel() ๋ฉ์๋๊ฐ ์์ผ๋ฉฐ, zoomLevel state ๋ณ์์ ๋๊ธฐํํ๋ ค๊ณ ํ ๊ฒ์
๋๋ค. Effect๋ ๋ค์๊ณผ ๋น์ทํ ๊ฒ์
๋๋ค.
useEffect(() => {
const map = mapRef.current;
map.setZoomLevel(zoomLevel);
}, [zoomLevel]);์ด ๊ฒฝ์ฐ์๋ ํด๋ฆฐ์
์ด ํ์ํ์ง ์์์ ์ ์ํ์ธ์. ๊ฐ๋ฐ ๋ชจ๋์์ React๋ Effect๋ฅผ ๋ ๋ฒ ํธ์ถํ์ง๋ง, ๋์ผํ ๊ฐ์ ๊ฐ์ง๊ณ setZoomLevel์ ๋ ๋ฒ ํธ์ถํ๋ ๊ฒ์ ์๋ฌด๋ฐ ๋ฌธ์ ๊ฐ ๋์ง ์์ต๋๋ค. ์ฝ๊ฐ ๋๋ฆด ์ ์์ง๋ง, ์ด๊ฒ์ ์ ํ ํ๊ฒฝ์์ ๋ถํ์ํ๊ฒ ๋ค์ ๋ง์ดํธ๋์ง ์๊ธฐ ๋๋ฌธ์ ๋ฌธ์ ๊ฐ ๋์ง ์์ต๋๋ค.
์ผ๋ถ API๋ ์ฐ์ํด์ ๋ ๋ฒ ํธ์ถํ๋ ๊ฒ์ ํ์ฉํ์ง ์์ ์๋ ์์ต๋๋ค. ์๋ฅผ ๋ค์ด ๋ด์ฅ๋ <dialog> ์์์ showModal ๋ฉ์๋๋ ๋ ๋ฒ ํธ์ถํ๋ฉด ์์ธ๋ฅผ ๋์ง๋๋ค. ํด๋ฆฐ์
ํจ์๋ฅผ ๊ตฌํํ๊ณ ์ด ํจ์์์ ๋ํ ์์๋ฅผ ๋ซ๋๋ก ๋ง๋ค์ด๋ณด์ธ์.
useEffect(() => {
const dialog = dialogRef.current;
dialog.showModal();
return () => dialog.close();
}, []);๊ฐ๋ฐ ์ค์๋ Effect๊ฐ showModal()์ ํธ์ถํ ๋ค์ ์ฆ์ close()๋ฅผ ํธ์ถํ๊ณ ๋ค์ showModal()์ ํธ์ถํฉ๋๋ค. ์ด๊ฒ์ ์ฌ์ฉ์๊ฐ ํ์ธํ ์ ์๋ ๋์์ด๋ฉฐ ์ ํ ํ๊ฒฝ์์ ๋ณผ ์ ์๋ ๊ฒ๊ณผ ๋์ผํฉ๋๋ค.
๋ง์ฝ Effect๊ฐ ์ด๋ค ๊ฒ์ ๊ตฌ๋ ํ๋ค๋ฉด, ํด๋ฆฐ์ ํจ์์์ ๊ตฌ๋ ์ ํด์งํด์ผ ํฉ๋๋ค.
useEffect(() => {
function handleScroll(e) {
console.log(window.scrollX, window.scrollY);
}
window.addEventListener('scroll', handleScroll);
return () => window.removeEventListener('scroll', handleScroll);
}, []);๊ฐ๋ฐ ์ค์๋ Effect๊ฐ addEventListener()๋ฅผ ํธ์ถํ ๋ค์ ์ฆ์ removeEventListener()๋ฅผ ํธ์ถํ๊ณ , ๊ทธ๋ค์ ๋์ผํ ํธ๋ค๋ฌ๋ก addEventListener()๋ฅผ ํธ์ถํฉ๋๋ค. ๋ฐ๋ผ์ ํ ๋ฒ์ ํ๋์ ํ์ฑ ๊ตฌ๋
๋ง ์๊ฒ ๋ฉ๋๋ค. ์ด๊ฒ์ ์ ํ ํ๊ฒฝ์์ ํ ๋ฒ addEventListener()๋ฅผ ํธ์ถํ๋ ๊ฒ๊ณผ ๋์ผํ ๋์์ ๊ฐ์ง๋๋ค.
Effect๊ฐ ์ด๋ค ์์๋ฅผ ์ ๋๋ฉ์ด์ ์ผ๋ก ํ์ํ๋ ๊ฒฝ์ฐ, ํด๋ฆฐ์ ํจ์์์ ์ ๋๋ฉ์ด์ ์ ์ด๊ธฐ ๊ฐ์ผ๋ก ์ฌ์ค์ ํด์ผ ํฉ๋๋ค.
useEffect(() => {
const node = ref.current;
node.style.opacity = 1; // Trigger the animation
return () => {
node.style.opacity = 0; // Reset to the initial value
};
}, []);๊ฐ๋ฐ ์ค์๋ ๋ถํฌ๋ช
๋๊ฐ 1๋ก ์ค์ ๋๊ณ , ๊ทธ๋ฐ ๋ค์ 0์ผ๋ก ์ค์ ๋๊ณ , ๋ค์ 1๋ก ์ค์ ๋ฉ๋๋ค. ์ด๊ฒ์ ์ ํ ํ๊ฒฝ์์ 1๋ก ์ง์ ์ค์ ํ๋ ๊ฒ๊ณผ ๋์ผํ ๋์์ ๊ฐ์ง๋๋ค. tweening์ ์ง์ํ๋ ์๋ํํฐ ์ ๋๋ฉ์ด์
๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ ํด๋ฆฐ์
ํจ์์์ ํ์๋ผ์ธ์ ์ด๊ธฐ ์ํ๋ก ์ฌ์ค์ ํด์ผ ํฉ๋๋ค.
๋ง์ฝ Effect๊ฐ ์ด๋ค ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์จ๋ค๋ฉด, ํด๋ฆฐ์ ํจ์์์๋ fetch๋ฅผ ์ค๋จํ๊ฑฐ๋ ๊ฒฐ๊ณผ๋ฅผ ๋ฌด์ํด์ผ ํฉ๋๋ค.
useEffect(() => {
let ignore = false;
async function startFetching() {
const json = await fetchTodos(userId);
if (!ignore) {
setTodos(json);
}
}
startFetching();
return () => {
ignore = true;
};
}, [userId]);์ด๋ฏธ ๋ฐ์ํ ๋คํธ์ํฌ ์์ฒญ์ "์คํ ์ทจ์"ํ ์๋ ์์ง๋ง, ํด๋ฆฐ์
ํจ์๋ ๋ ์ด์ ๊ด๋ จ์ด ์๋ ํ์น๊ฐ ์ ํ๋ฆฌ์ผ์ด์
์ ๊ณ์ ์ํฅ์ ๋ฏธ์น์ง ์๋๋ก ๋ณด์ฅํด์ผ ํฉ๋๋ค. userId๊ฐ 'Alice'์์ 'Bob'์ผ๋ก ๋ณ๊ฒฝ๋๋ฉด ํด๋ฆฐ์
์ 'Bob'์ดํ์ ๋์ฐฉํ๋๋ผ๋ 'Alice' ์๋ต์ ๋ฌด์ํ๋๋ก ๋ณด์ฅํฉ๋๋ค.
๊ฐ๋ฐ ์ค์๋ ๋คํธ์ํฌ ํญ์์ ๋ ๊ฐ์ ํ์น๊ฐ ํ์๋ฉ๋๋ค. ์ด๋ ๋ฌธ์ ๊ฐ ์์ต๋๋ค. ์์ ์ ๊ทผ ๋ฐฉ์์ ์ฌ์ฉํ๋ฉด ์ฒซ ๋ฒ์งธ Effect๋ ์ฆ์ ํด๋ฆฐ์
๋์ด ignore ๋ณ์์ ๋ณต์ฌ๋ณธ์ด true๋ก ์ค์ ๋ฉ๋๋ค. ๋ฐ๋ผ์ ์ถ๊ฐ ์์ฒญ์ด ์๋๋ผ๋ if (!ignore) ๊ฒ์ฌ ๋๋ถ์ state์ ์ํฅ์ ๋ฏธ์น์ง ์์ต๋๋ค.
์ ํ ํ๊ฒฝ์์๋ ํ๋์ ์์ฒญ๋ง ์์ ๊ฒ์ ๋๋ค. ๊ฐ๋ฐ ์ค์ ๋ ๋ฒ์งธ ์์ฒญ์ด ๋ฌธ์ ๋ผ๋ฉด, ๊ฐ์ฅ ์ข์ ๋ฐฉ๋ฒ์ ์ค๋ณต ์์ฒญ์ ์ ๊ฑฐํ๊ณ ์ปดํฌ๋ํธ ๊ฐ์ ์๋ต์ ์บ์ํ๋ ์๋ฃจ์ ์ ์ฌ์ฉํ๋ ๊ฒ์ ๋๋ค:
function TodoList() {
const todos = useSomeDataLibrary(`/api/user/${userId}/todos`);
// ...์ด๋ ๊ฒ ํ๋ฉด ๊ฐ๋ฐ ํ๊ฒฝ์ ๊ฐ์ ํ๋๋ฐ ๋์์ด ๋ ๋ฟ๋ง ์๋๋ผ ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ฐ์ ์๋๋ ํฅ์๋ฉ๋๋ค. ์๋ฅผ ๋ค์ด ์ฌ์ฉ์๊ฐ ๋ค๋ก ๊ฐ๊ธฐ ๋ฒํผ์ ๋๋ ์ ๋ ๋ฐ์ดํฐ๋ฅผ ๋ค์ ๋ก๋ํ๋ ๊ฒ์ ๊ธฐ๋ค๋ฆด ํ์๊ฐ ์์ต๋๋ค. ๋ฐ์ดํฐ๊ฐ ์บ์๋๊ธฐ ๋๋ฌธ์ ๋๋ค. ์ด๋ฌํ ์บ์๋ฅผ ์ง์ ๊ตฌ์ถํ๊ฑฐ๋ ๋น์ทํ ํจ๊ณผ๋ฅผ ๋๋ฆด ์ ์๋ ์ฌ๋ฌ ๋์ ์ค ํ๋๋ฅผ ์ฌ์ฉํ ์ ์์ต๋๋ค.
Effect์์ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๋ ์ข์ ๋์์ ๋ฌด์์ธ๊ฐ์? {/what-are-good-alternatives-to-data-fetching-in-effects/}
Effect ์์์ fetch ํธ์ถ์ ์์ฑํ๋ ๊ฒ์ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๋ ์ธ๊ธฐ ์๋ ๋ฐฉ๋ฒ์
๋๋ค, ํนํ ์์ ํ ํด๋ผ์ด์ธํธ ์ธก ์ฑ์์๋์. ํ์ง๋ง ์ด๋ ๋งค์ฐ ์๋์ ์ธ ์ ๊ทผ ๋ฐฉ์์ด๋ฉฐ ์ค์ํ ๋จ์ ์ด ์์ต๋๋ค.
- Effect๋ ์๋ฒ์์ ์คํ๋์ง ์์ต๋๋ค. ๋ฐ๋ผ์ ์ด๊ธฐ ์๋ฒ ๋ ๋๋ง๋ HTML์ ๋ฐ์ดํฐ๊ฐ ์๋ ๋ก๋ฉ ์ํ๋ง ํฌํจํ๊ฒ ๋ฉ๋๋ค. ํด๋ผ์ด์ธํธ ์ปดํจํฐ๋ ๋ชจ๋ JavaScript๋ฅผ ๋ค์ด๋ก๋ํ๊ณ ์ฑ์ ๋ ๋๋งํด์ผ๋ง ๋ฐ์ดํฐ๋ฅผ ๋ก๋ํด์ผ ํ๋ค๋ ๊ฒ์ ์๊ฒ ๋ ๊ฒ์ ๋๋ค. ์ด๋ ํจ์จ์ ์ด์ง ์์ต๋๋ค.
- Effect ์์์ ์ง์ ๊ฐ์ ธ์ค๋ฉด "๋คํธ์ํฌ ํญํฌ"๋ฅผ ์ฝ๊ฒ ๋ง๋ค ์ ์์ต๋๋ค. ๋ถ๋ชจ ์ปดํฌ๋ํธ๋ฅผ ๋ ๋๋งํ๋ฉด ์ผ๋ถ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๊ณ ์์ ์ปดํฌ๋ํธ๋ฅผ ๋ ๋๋งํ ๋ค์ ๊ทธ๋ค์ด ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๊ธฐ ์์ํฉ๋๋ค. ๋คํธ์ํฌ๊ฐ ๋น ๋ฅด์ง ์์ผ๋ฉด ์ด๋ ๋ชจ๋ ๋ฐ์ดํฐ๋ฅผ ๋ณ๋ ฌ๋ก ๊ฐ์ ธ์ค๋ ๊ฒ๋ณด๋ค ํจ์ฌ ๋๋ฆฝ๋๋ค.
- Effect ์์์ ์ง์ ๊ฐ์ ธ์ค๋ ๊ฒ์ ์ผ๋ฐ์ ์ผ๋ก ๋ฐ์ดํฐ๋ฅผ ๋ฏธ๋ฆฌ ๋ก๋ํ๊ฑฐ๋ ์บ์ํ์ง ์์์ ์๋ฏธํฉ๋๋ค. ์๋ฅผ ๋ค์ด ์ปดํฌ๋ํธ๊ฐ ๋ง์ดํธ ํด์ ๋๊ณ ๋ค์ ๋ง์ดํธ๋๋ฉด ๋ฐ์ดํฐ๋ฅผ ๋ค์ ๊ฐ์ ธ์์ผ ํฉ๋๋ค.
- ๊ทธ๋ฆฌ ํธ๋ฆฌํ์ง ์์ต๋๋ค.
fetchํธ์ถ์ ์์ฑํ ๋ ๊ฒฝ์ ์ํ์ ๊ฐ์ ๋ฒ๊ทธ์ ์ํฅ์ ๋ฐ์ง ์๋ ๋ฐฉ์์ผ๋ก ์์ฑํ๋ ๋ฐ ๊ฝค ๋ง์ ๋ณด์ผ๋ฌํ๋ ์ดํธ ์ฝ๋๊ฐ ํ์ํฉ๋๋ค.
์ด ๋จ์ ๋ชฉ๋ก์ React์๋ง ํด๋น๋๋ ๊ฒ์ ์๋๋๋ค. ์ด๋ค ๋ผ์ด๋ธ๋ฌ๋ฆฌ์์๋ ๋ง์ดํธ ์์ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์จ๋ค๋ฉด ๋น์ทํ ๋จ์ ์ด ์กด์ฌํฉ๋๋ค. ๋ง์ดํธ ์์ ๋ฐ์ดํฐ๋ฅผ ํ์นญํ๋ ๊ฒ๋ ๋ผ์ฐํ ๊ณผ ๋ง์ฐฌ๊ฐ์ง๋ก ์ ์ํํ๊ธฐ ์ด๋ ค์ด ์์ ์ด๋ฏ๋ก ๋ค์ ์ ๊ทผ ๋ฐฉ์์ ๊ถ์ฅํฉ๋๋ค.
- ํ๋ ์์ํฌ๋ฅผ ์ฌ์ฉํ๊ณ ์๋ค๋ฉด, ๊ทธ ํ๋ ์์ํฌ๊ฐ ์ ๊ณตํ๋ ๋ด์ฅ ๋ฐ์ดํฐ ํจ์นญ ๊ธฐ๋ฅ์ ์ฌ์ฉํ์ธ์. ์ต์ React ํ๋ ์์ํฌ๋ ํจ์จ์ ์ธ ๋ฐ์ดํฐ ํจ์นญ ๋ฉ์ปค๋์ฆ์ ๋ด์ฅํ๊ณ ์์ผ๋ฉฐ, ์์ ์ธ๊ธํ ๋จ์ ์ ๊ฒช์ง ์์ต๋๋ค.
- ์ฌ์ฉํ์ง ์๋๋ค๋ฉด, ํด๋ผ์ด์ธํธ ์ฌ์ด๋ ์บ์๋ฅผ ์ฌ์ฉํ๊ฑฐ๋ ์ง์ ๊ตฌ์ถํ๋ ๊ฒ์ ๊ณ ๋ คํ์ธ์. ์ธ๊ธฐ์๋ ์คํ์์ค ์๋ฃจ์ ์ผ๋ก๋ React Query, useSWR, React Router 6.4+๊ฐ ์์ต๋๋ค. ์ง์ ๊ตฌํํ ์๋ ์๋๋ฐ, ์ด ๊ฒฝ์ฐ์๋ Effects๋ฅผ ์ฌ์ฉํ๋, ์์ฒญ ์ค๋ณต ์ ๊ฑฐ, ์๋ต ์บ์ฑ, ๋คํธ์ํฌ ์ํฐํด ๋ฐฉ์ง๋ฅผ ์ํ ๋ก์ง์ ์ถ๊ฐํด์ผ ํฉ๋๋ค(๋ฐ์ดํฐ๋ฅผ ๋ฏธ๋ฆฌ ๋ก๋ํ๊ฑฐ๋, ํ์ํ ๋ฐ์ดํฐ๋ฅผ ์์ ๋ผ์ฐํธ๋ก ํธ์ด์คํ ํ๋ ๋ฐฉ์์ผ๋ก).
์ด๋ฌํ ์ ๊ทผ ๋ฐฉ์ ์ค ์ด๋ ๊ฒ๋ ์ ํฉํ์ง ์์ ๊ฒฝ์ฐ, Effect ๋ด์์ ๋ฐ์ดํฐ๋ฅผ ์ง์ ๊ฐ์ ธ์ค๋ ๊ฒ์ ๊ณ์ํ์ ๋ ๋ฉ๋๋ค.
ํ์ด์ง ๋ฐฉ๋ฌธ ์ ๋ถ์ ์ด๋ฒคํธ๋ฅผ ๋ณด๋ด๋ ๋ค์ ์ฝ๋๋ฅผ ๊ณ ๋ คํด๋ณด์ธ์.
useEffect(() => {
logVisit(url); // POST ์์ฒญ์ ๋ณด๋
}, [url]);๊ฐ๋ฐ ํ๊ฒฝ์์๋ logVisit๊ฐ ๊ฐ URL์ ๋ํด ๋ ๋ฒ ํธ์ถ๋ ๊ฒ์
๋๋ค. ๊ทธ๋์ ์ด๋ฅผ ์์ ํ๊ณ ์ถ์ ์ ์์ต๋๋ค. ์ฐ๋ฆฌ๋ ์ด ์ฝ๋๋ฅผ ๊ทธ๋๋ก ์ ์งํ๋ ๊ฒ์ ๊ถ์ฅํฉ๋๋ค. ์ด์ ์์์ ๋ง์ฐฌ๊ฐ์ง๋ก ํ ๋ฒ ์คํํ๊ฑฐ๋ ๋ ๋ฒ ์คํํ๋ ๊ฒ ์ฌ์ด์์ ์ฌ์ฉ์๊ฐ ๋ณผ ์ ์๋ ๋์ ์ฐจ์ด๊ฐ ์์ต๋๋ค. ์ค์ ๋ก ๊ฐ๋ฐ ํ๊ฒฝ์์๋ logVisit๊ฐ ์๋ฌด ์์
๋ ์ํํ์ง ์์์ผ ํฉ๋๋ค. ์๋ํ๋ฉด ๊ฐ๋ฐ ํ๊ฒฝ์ ๋ก๊ทธ๊ฐ ์ ํ ์งํ๋ฅผ ์๊ณก์ํค์ง ์๋๋ก ํ๊ธฐ ์ํจ์
๋๋ค. ์ปดํฌ๋ํธ๋ ํ์ผ์ ์ ์ฅํ ๋๋ง๋ค ์ฌ๋ง์ดํธ๋๋ฏ๋ก ๊ฐ๋ฐ ํ๊ฒฝ์์๋ ์ถ๊ฐ์ ์ธ ๋ฐฉ๋ฌธ ๊ธฐ๋ก์ ๋ก๊ทธ์ ๋จ๊ธฐ๊ฒ ๋ฉ๋๋ค.
์ ํ ํ๊ฒฝ์์๋ ์ค๋ณต๋ ๋ฐฉ๋ฌธ ๋ก๊ทธ๊ฐ ์์ ๊ฒ์ ๋๋ค.
๋ณด๋ด๋ ๋ถ์ ์ด๋ฒคํธ๋ฅผ ๋๋ฒ๊น ํ๋ ค๋ฉด ์ฑ์ ์คํ ์ด์ง ํ๊ฒฝ(์ ํ ๋ชจ๋๋ก ์คํ)์ ๋ฐฐํฌํ๊ฑฐ๋ Strict Mode๋ฅผ ์ผ์์ ์ผ๋ก ์ฌ์ฉ ์ค์งํ์ฌ ๊ฐ๋ฐ ํ๊ฒฝ ์ ์ฉ์ ์ฌ๋ง์ดํ ๊ฒ์ฌ๋ฅผ ์ํํ ์ ์์ต๋๋ค. ๋ํ Effect ๋์ ๋ผ์ฐํธ ๋ณ๊ฒฝ ์ด๋ฒคํธ ํธ๋ค๋ฌ์์ ๋ถ์์ ๋ณด๋ผ ์๋ ์์ต๋๋ค. ๋ ์ ๋ฐํ ๋ถ์์ ์ํด Intersection Observer๋ฅผ ์ฌ์ฉํ์ฌ ์ด๋ค ์ปดํฌ๋ํธ๊ฐ ๋ทฐํฌํธ์ ์๋์ง์ ์ผ๋ง๋ ์ค๋ ๋ณด์ด๋์ง ์ถ์ ํ๋ ๋ฐ ๋์์ด ๋ ์ ์์ต๋๋ค.
Effect๊ฐ ์๋ ๊ฒฝ์ฐ: ์ ํ๋ฆฌ์ผ์ด์ ์ด๊ธฐํ {/not-an-effect-initializing-the-application/}
์ผ๋ถ ๋ก์ง์ ์ ํ๋ฆฌ์ผ์ด์ ์์ ์์ ํ ๋ฒ๋ง ์คํ๋์ด์ผ ํฉ๋๋ค. ์ด๋ฌํ ๋ก์ง์ ์ปดํฌ๋ํธ ์ธ๋ถ์ ๋ฐฐ์นํ ์ ์์ต๋๋ค.
if (typeof window !== 'undefined') { // ๋ธ๋ผ์ฐ์ ์์ ์คํ ์ค์ธ์ง ํ์ธํฉ๋๋ค.
checkAuthToken();
loadDataFromLocalStorage();
}
function App() {
// ...
}์์ ๊ฐ์ด ์ปดํฌ๋ํธ ์ธ๋ถ์์ ํด๋น ๋ก์ง์ ์คํํ๋ฉด, ํด๋น ๋ก์ง์ ๋ธ๋ผ์ฐ์ ๊ฐ ํ์ด์ง๋ฅผ ๋ก๋ํ ํ ํ ๋ฒ๋ง ์คํ๋จ์ด ๋ณด์ฅ๋ฉ๋๋ค.
๊ฐ๋์ ํด๋ฆฐ์ ํจ์๋ฅผ ์์ฑํ๋๋ผ๋ Effect๊ฐ ๋ ๋ฒ ์คํ๋๋ ๊ฒ์ ๋ํด ์ฌ์ฉ์๊ฐ ํ์ธํ ์ ์๋ ๊ฒฐ๊ณผ๋ฅผ ๋ฐฉ์งํ ๋ฐฉ๋ฒ์ด ์์ ์ ์์ต๋๋ค. ์๋ฅผ ๋ค์ด, ์๋์ ๊ฐ์ด ์ ํ์ ๊ตฌ๋งคํ๋ POST ์์ฒญ์ ๋ณด๋ด๋ Effect๊ฐ ์๋ค๊ณ ๊ฐ์ ํด ๋ณด๊ฒ ์ต๋๋ค.
useEffect(() => {
// ๐ด ์๋ชป๋ ๋ฐฉ๋ฒ: ์ด Effect๋ ๊ฐ๋ฐ ํ๊ฒฝ์์ ๋ ๋ฒ ์คํ๋๋ฉฐ ์ฝ๋์ ๋ฌธ์ ๊ฐ ๋๋ฌ๋ฉ๋๋ค.
fetch('/api/buy', { method: 'POST' });
}, []);์ฌ์ฉ์๋ ์ ํ์ ๋ ๋ฒ ๊ตฌ๋งคํ๊ณ ์ถ์ง ์์ ๊ฒ์ ๋๋ค. ๊ทธ๋ฌ๋ ์ด๊ฒ์ ์ด๋ฌํ ๋ก์ง์ Effect์ ๋ฃ์ง ์์์ผ ํ๋ ์ด์ ์ ๋๋ค. ์ฌ์ฉ์๊ฐ ๋ค๋ฅธ ํ์ด์ง๋ก ์ด๋ํ ๋ค์ ๋ค๋ก ๊ฐ๊ธฐ ๋ฒํผ์ ๋๋ฅด๋ ๊ฒฝ์ฐ ์ด๋ป๊ฒ ๋ ๊น์? Effect๊ฐ ๋ค์ ์คํ๋ฉ๋๋ค. ์ฌ์ฉ์๊ฐ ํ์ด์ง๋ฅผ ๋ฐฉ๋ฌธํ ๋ ์ ํ์ ๊ตฌ๋งคํ๋ ค๊ณ ํ์ง ์์ผ๋ฉฐ, ์ฌ์ฉ์๊ฐ "๊ตฌ๋งค" ๋ฒํผ์ ํด๋ฆญํ ๋ ์ ํ์ ๊ตฌ๋งคํ๊ณ ์ถ์ ๊ฒ์ ๋๋ค.
๊ตฌ๋งค๋ ๋ ๋๋ง์ ์ํด ๋ฐ์ํ๋ ๊ฒ์ด ์๋๋ผ ํน์ ์ํธ ์์ฉ์ ์ํด ๋ฐ์ํฉ๋๋ค. ์ฌ์ฉ์๊ฐ ๋ฒํผ์ ๋๋ฅผ ๋๋ง ์คํ๋์ด์ผ ํฉ๋๋ค. Effect๋ฅผ ์ญ์ ํ๊ณ /api/buy ์์ฒญ์ Buy ๋ฒํผ์ ์ด๋ฒคํธ ํธ๋ค๋ฌ๋ก ์ด๋ํ์ธ์.
function handleClick() {
// โ
๊ตฌ๋งค๋ ํน์ ์ํธ ์์ฉ์ ์ํด ๋ฐ์ํ๋ ์ด๋ฒคํธ์
๋๋ค.
fetch('/api/buy', { method: 'POST' });
}๋ง์ฝ ์ปดํฌ๋ํธ๋ฅผ ๋ค์ ๋ง์ดํธํ์ ๋ ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ก์ง์ด ๊นจ์ง๋ค๋ฉด, ๊ธฐ์กด์ ์กด์ฌํ๋ ๋ฒ๊ทธ๊ฐ ๋๋ฌ๋ ๊ฒ์ ๋๋ค. ์ฌ์ฉ์์ ๊ด์ ์์ ํ์ด์ง๋ฅผ ๋ฐฉ๋ฌธํ๋ ๊ฒ๊ณผ ํ์ด์ง๋ฅผ ๋ฐฉ๋ฌธํ๊ณ , ๋งํฌ๋ฅผ ํด๋ฆญํ ๋ค์, ๋ค๋ก ๊ฐ๊ธฐ ๋ฒํผ์ ๋๋ฌ์ ๋ค์ ํ์ด์ง๋ก ๋์์จ๊ฒ ๊ณผ ์ฐจ์ด๊ฐ ์์ด์ผ ํฉ๋๋ค. React๋ ๊ฐ๋ฐ ํ๊ฒฝ์์ ์ปดํฌ๋ํธ๋ฅผ ํ ๋ฒ ๋ค์ ๋ง์ดํธํ์ฌ ์ด ์์น์ ์ค์ํ๋์ง ํ์ธํฉ๋๋ค.
์ด ํ๋ ์ด๊ทธ๋ผ์ด๋๋ฅผ ์ดํด๋ณด๋ฉด ์ค์ ๋ก Effect๊ฐ ์ด๋ป๊ฒ ์๋ํ๋์ง์ ๋ํ "๋๋์ ์ป์" ์ ์์ต๋๋ค.
์ด ์์๋ setTimeout์ ์ฌ์ฉํ์ฌ Effect๊ฐ ์คํ๋ ํ 3์ด ํ์ ์
๋ ฅ ํ
์คํธ์ ํจ๊ป ์ฝ์ ๋ก๊ทธ๊ฐ ํ์๋๋๋ก ํฉ๋๋ค. ํด๋ฆฐ์
ํจ์๋ ์คํ์ ๊ธฐ๋ค๋ฆฌ๋ ํ์์์์ ์ทจ์ํฉ๋๋ค. "์ปดํฌ๋ํธ ๋ง์ดํธ" ๋ฒํผ์ ๋๋ฌ ์์ํ์ธ์.
import { useState, useEffect } from 'react';
function Playground() {
const [text, setText] = useState('a');
useEffect(() => {
function onTimeout() {
console.log('โฐ ' + text);
}
console.log('๐ต ์ค์ผ์ค ๋ก๊ทธ "' + text);
const timeoutId = setTimeout(onTimeout, 3000);
return () => {
console.log('๐ก ์ทจ์ ๋ก๊ทธ "' + text);
clearTimeout(timeoutId);
};
}, [text]);
return (
<>
<label>
What to log:{' '}
<input
value={text}
onChange={e => setText(e.target.value)}
/>
</label>
<h1>{text}</h1>
</>
);
}
export default function App() {
const [show, setShow] = useState(false);
return (
<>
<button onClick={() => setShow(!show)}>
์ปดํฌ๋ํธ {show ? '๋ง์ดํธ ํด์ ' : '๋ง์ดํธ'}
</button>
{show && <hr />}
{show && <Playground />}
</>
);
}์ฒ์์๋ Schedule "a" log, Cancel "a" log, ๊ทธ๋ฆฌ๊ณ ๋ค์ Schedule "a" log ๋ผ๋ ์ธ ๊ฐ์ง ๋ก๊ทธ๋ฅผ ๋ณผ ์ ์์ ๊ฒ์
๋๋ค. ๋ช ์ด ํ์๋ a๋ผ๋ ๋ก๊ทธ๊ฐ ๋ํ๋ ๊ฒ์
๋๋ค. ์ด์ ์ ๋ฐฐ์ด ๋ด์ฉ์ฒ๋ผ ์ถ๊ฐ๋ ์ค์ผ์ค/์ทจ์ ์์ React๊ฐ ์ปดํฌ๋ํธ๋ฅผ ๊ฐ๋ฐ ์ค์ ํ ๋ฒ ๋ค์ ๋ง์ดํธํ์ฌ ์ ๋ฆฌ๋ฅผ ์ ๋๋ก ๊ตฌํํ๋์ง ํ์ธํ๊ธฐ ๋๋ฌธ์
๋๋ค.
์ด์ ์
๋ ฅ๋์ abc๋ก ์์ ํด ๋ณด์ธ์. ์ถฉ๋ถํ ๋น ๋ฅด๊ฒ ์
๋ ฅํ๋ฉด Schedule "ab" log ๋ฐ๋ก ๋ค์ Cancel "ab" log์ Schedule "abc" log๊ฐ ๋ํ๋ ๊ฒ์
๋๋ค. React๋ ํญ์ ์ด์ ๋ ๋์ Effect๋ฅผ ๋ค์ ๋ ๋์ Effect๋ณด๋ค ๋จผ์ ์ ๋ฆฌํฉ๋๋ค. ๋ฐ๋ผ์ ๋น ๋ฅด๊ฒ ์
๋ ฅํ๋๋ผ๋ ํ ๋ฒ์ ์ต๋ ํ๋์ ํ์์์๋ง ์์ฝ๋๋ ๊ฒ์ ๋ณผ ์ ์์ต๋๋ค. ์
๋ ฅ์ ๋ช ๋ฒ ํด๋ณด๋ฉด์ Effect๊ฐ ์ด๋ป๊ฒ ์ ๋ฆฌ๋๋์ง ๋๊ปด๋ณด์ธ์.
์ ๋ ฅ๋์ ๋ฌด์ธ๊ฐ๋ฅผ ์ ๋ ฅํ ๋ค์ "์ปดํฌ๋ํธ ๋ง์ดํธ ํด์ "๋ฅผ ๋๋ฌ๋ณด์ธ์. ๋ง์ดํธ ํด์ ๊ฐ ๋ง์ง๋ง ๋ ๋์ Effect๋ฅผ ์ ๋ฆฌํจ์ ์ฃผ๋ชฉํ์ธ์. ์ฌ๊ธฐ์๋ ํ์์์์ด ์คํ๋๊ธฐ ์ ์ ๋ง์ง๋ง ํ์์์์ด ์ทจ์๋ฉ๋๋ค.
๋ง์ง๋ง์ผ๋ก ์ ์ปดํฌ๋ํธ๋ฅผ ์์ ํ๊ณ ์ ๋ฆฌ ํจ์๋ฅผ ์ฃผ์ ์ฒ๋ฆฌํ์ฌ ํ์์์์ด ์ทจ์๋์ง ์๋๋ก ํด๋ณด์ธ์. abcde๋ฅผ ๋น ๋ฅด๊ฒ ์
๋ ฅํด ๋ณด์ธ์. ๋ช ์ด ํ์ ๋ฌด์์ด ๊ธฐ๋๋๋์ง ์๊ฐํด ๋ณด์ธ์. ํ์์์ ๋ด๋ถ์ console.log(text)๊ฐ ๊ฐ์ฅ ์ต๊ทผ์ text๋ฅผ ์ถ๋ ฅํ๊ณ ๋ค์ฏ ๋ฒ์ abcde ๋ก๊ทธ๊ฐ ์์ฑ๋ ๊น์? ์ง์ ์๋ํ์ฌ ํ์ธํด ๋ณด์ธ์!
์ ์ด ํ์ a, ab, abc, abcd, ๊ทธ๋ฆฌ๊ณ abcde๋ผ๋ ์ผ๋ จ์ ๋ก๊ทธ๋ฅผ ๋ณผ ์ ์์ ๊ฒ์
๋๋ค. ๊ฐ Effect๋ ํด๋น ๋ ๋์ text ๊ฐ์ "์บก์ฒ"ํฉ๋๋ค. text ์ํ๊ฐ ๋ณ๊ฒฝ๋์๋์ง ์ฌ๋ถ๋ ์ค์ํ์ง ์์ต๋๋ค. text = 'ab' ๋ ๋์ Effect์์๋ ํญ์ 'ab'๋ฅผ ๋ณผ ๊ฒ์
๋๋ค. ๋ค์ ๋งํด, ๊ฐ ๋ ๋์ Effect๋ ์๋ก ๊ฒฉ๋ฆฌ๋์ด ์์ต๋๋ค. ์ด ์๋ ๋ฐฉ์์ ๋ํด์ ๊ถ๊ธํ๋ค๋ฉด ํด๋ก์ ์ ๋ํด ์ฝ์ด๋ณผ ์ ์์ต๋๋ค.
๊ฐ๊ฐ์ ๋ ๋๋ง์ ๊ฐ๊ฐ์ ๊ณ ์ ํ Effect๋ฅผ ๊ฐ์ต๋๋ค. {/each-render-has-its-own-effects/}
useEffect๋ฅผ ๋ ๋๋ง ๊ฒฐ๊ณผ๋ฌผ์ "๋ถ์ฐฉ"ํ๋ ๊ฒ์ผ๋ก ์๊ฐํ ์ ์์ต๋๋ค. ๋ค์๊ณผ ๊ฐ์ Effect๋ฅผ ๊ณ ๋ คํด ๋ณด์ธ์.
export default function ChatRoom({ roomId }) {
useEffect(() => {
const connection = createConnection(roomId);
connection.connect();
return () => connection.disconnect();
}, [roomId]);
return <h1>Welcome to {roomId}!</h1>;
}์ด์ ์ฌ์ฉ์๊ฐ ์ฑ์ ํ์ํ๋ ๋์ ์ ํํ ์ด๋ค ์ผ์ด ์ผ์ด๋๋์ง ์์๋ณด๊ฒ ์ต๋๋ค.
์ฌ์ฉ์๊ฐ <ChatRoom roomId="general" />์ ๋ฐฉ๋ฌธํฉ๋๋ค. ์ด๋, roomId๋ฅผ 'general'๋ก ๋ฉํ๋ชจ๋ธ ์์์ ๋์ฒดํด๋ณด๊ฒ ์ต๋๋ค.
// ์ฒซ ๋ฒ์งธ ๋ ๋๋ง์ ๋ํ JSX (roomId = "general")
return <h1>Welcome to general!</h1>;Effect ๋ํ ๋ ๋๋ง ๊ฒฐ๊ณผ๋ฌผ์ ์ผ๋ถ์ ๋๋ค. ์ฒซ ๋ฒ์งธ ๋ ๋๋ง์ Effect๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
// ์ฒซ ๋ฒ์งธ ๋ ๋๋ง์ ๋ํ ์ดํํธ (roomId = "general")
() => {
const connection = createConnection('general');
connection.connect();
return () => connection.disconnect();
},
// ์ฒซ ๋ฒ์งธ ๋ ๋๋ง์ ์์กด์ฑ (roomId = "general")
['general']React๋ ์ด Effect๋ฅผ ์คํํ๋ฉฐ, 'general' ์ฑํ
๋ฐฉ์ ์ฐ๊ฒฐํฉ๋๋ค.
<ChatRoom roomId="general" />๊ฐ ๋ค์ ๋ ๋๋ง๋๋ค๊ณ ๊ฐ์ ํด๋ด
์๋ค. JSX ๊ฒฐ๊ณผ๋ฌผ์ ๋์ผํฉ๋๋ค.
// ๋ ๋ฒ์งธ ๋ ๋๋ง์ ๋ํ JSX (roomId = "general")
return <h1>Welcome to general!</h1>;React๋ ๋ ๋๋ง ์ถ๋ ฅ์ด ๋ณ๊ฒฝ๋์ง ์์๊ธฐ ๋๋ฌธ์ DOM์ ์ ๋ฐ์ดํธํ์ง ์์ต๋๋ค.
๋ ๋ฒ์งธ ๋ ๋๋ง์์์ Effect๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
// ๋ ๋ฒ์งธ ๋ ๋๋ง์ ๋ํ Effect (roomId = "general")
() => {
const connection = createConnection('general');
connection.connect();
return () => connection.disconnect();
},
// ๋ ๋ฒ์งธ ๋ ๋๋ง์ ๋ํ ์์กด์ฑ (roomId = "general")
['general']React๋ ๋ ๋ฒ์งธ ๋ ๋๋ง์์์ ['general']๋ฅผ ์ฒซ ๋ฒ์งธ ๋ ๋๋ง์์์ ['general']์ ๋น๊ตํฉ๋๋ค. ๋ชจ๋ ์์กด์ฑ์ด ๋์ผํ๋ฏ๋ก React๋ ๋ ๋ฒ์งธ ๋ ๋๋ง์์์ Effect๋ฅผ ๋ฌด์ํฉ๋๋ค. ํด๋น Effect๋ ํธ์ถ๋์ง ์์ต๋๋ค.
๊ทธ๋ผ, ์ฌ์ฉ์๊ฐ <ChatRoom roomId="travel" />์ ํ์ํฉ๋๋ค. ์ด๋ฒ์๋ ์ปดํฌ๋ํธ๊ฐ ๋ค๋ฅธ JSX๋ฅผ ๋ฐํํฉ๋๋ค.
// ์ธ ๋ฒ์งธ ๋ ๋๋ง์ ๋ํ JSX (roomId = "travel")
return <h1>Welcome to travel!</h1>;React๋ DOM์ ์
๋ฐ์ดํธํ์ฌ "Welcome to general"์ "Welcome to travel"๋ก ๋ณ๊ฒฝํฉ๋๋ค.
์ธ ๋ฒ์งธ ๋ ๋๋ง์์์ Effect๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค:
// ์ธ ๋ฒ์งธ ๋ ๋๋ง์ ๋ํ Effect (roomId = "travel")
() => {
const connection = createConnection('travel');
connection.connect();
return () => connection.disconnect();
},
// ์ธ ๋ฒ์งธ ๋ ๋๋ง์ ๋ํ ์์กด์ฑ (roomId = "travel")
['travel']React๋ ์ธ ๋ฒ์งธ ๋ ๋๋ง์์์ ['travel']์ ๋ ๋ฒ์งธ ๋ ๋๋ง์์์ ['general']๋ฅผ ๋น๊ตํฉ๋๋ค. ํ๋์ ์์กด์ฑ์ด ๋ค๋ฆ
๋๋ค: Object.is('travel', 'general')์ false์
๋๋ค. Effect๋ ๊ฑด๋๋ธ ์ ์์ต๋๋ค.
React๋ ์ธ ๋ฒ์งธ ๋ ๋๋ง์ Effect๋ฅผ ์ ์ฉํ๊ธฐ ์ ์ ๋จผ์ ์คํ๋ Effect๋ฅผ ์ ๋ฆฌํด์ผ ํฉ๋๋ค. ๋ ๋ฒ์งธ ๋ ๋๋ง์ Effect๊ฐ ๊ฑด๋๋ฐ์ด์ก๊ธฐ ๋๋ฌธ์, React๋ ์ฒซ ๋ฒ์งธ ๋ ๋๋ง์ Effect๋ฅผ ์ ๋ฆฌํด์ผ ํฉ๋๋ค. ์ฒ์ ๋ ๋๋ง๋์์ ๋ ์คํฌ๋กคํ๋ฉด, createConnection('general')๋ก ์์ฑ๋ ์ฐ๊ฒฐ์ ๋ํด disconnect()๋ฅผ ํธ์ถํ๋ ๊ฒ์ ๋ณผ ์ ์์ต๋๋ค. ์ด๋ก์จ ์ฑ์ 'general' ์ฑํ
๋ฐฉ๊ณผ์ ์ฐ๊ฒฐ์ด ํด์ ๋ฉ๋๋ค.
๊ทธ ํ์ React๋ ์ธ ๋ฒ์งธ ๋ ๋๋ง์ Effect๋ฅผ ์คํํฉ๋๋ค. 'travel' ์ฑํ
๋ฐฉ์ ์ฐ๊ฒฐํฉ๋๋ค.
๋ง์ง๋ง์ผ๋ก, ์ฌ์ฉ์๊ฐ ๋ค๋ฅธ ํ์ด์ง๋ก ์ด๋ํ๊ฒ ๋์ด ChatRoom ์ปดํฌ๋ํธ๊ฐ ๋ง์ดํธ ํด์ ๋ฉ๋๋ค. React๋ ๋ง์ง๋ง Effect์ ํด๋ฆฐ์
ํจ์๋ฅผ ์คํํฉ๋๋ค. ๋ง์ง๋ง Effect๋ ์ธ ๋ฒ์งธ ๋ ๋๋ง์์ ์จ ๊ฒ์
๋๋ค. ์ธ ๋ฒ์งธ ๋ ๋๋ง์ ํด๋ฆฐ์
์ createConnection('travel') ์ฐ๊ฒฐ์ ์ข
๋ฃํฉ๋๋ค. ๊ทธ๋์ ์ฑ์ 'travel' ์ฑํ
๋ฐฉ๊ณผ์ ์ฐ๊ฒฐ์ ํด์ ํ๊ฒ ๋ฉ๋๋ค.
Strict Mode๊ฐ ํ์ฑํ๋ ๊ฒฝ์ฐ, React๋ ๋ชจ๋ ์ปดํฌ๋ํธ๋ฅผ ํ ๋ฒ ๋ง์ดํธํ ํ์ ๋ค์ ๋ง์ดํธํฉ๋๋ค(state์ DOM์ ๋ณด์กด๋ฉ๋๋ค). ์ด๋ ํด๋ฆฐ์ ์ด ํ์ํ Effect๋ฅผ ์ฐพ๋ ๋ฐ ๋์์ด ๋๋ฉฐ ๊ฒฝ์ ์กฐ๊ฑด๊ณผ ๊ฐ์ ๋ฒ๊ทธ๋ฅผ ์ด๊ธฐ์ ๋๋ฌ๋ ์ ์๊ฒ ํฉ๋๋ค. ๊ฒ๋ค๊ฐ React๋ ๊ฐ๋ฐ ์ค ํ์ผ์ ์ ์ฅํ ๋๋ง๋ค Effect๋ฅผ ๋ค์ ๋ง์ดํธํฉ๋๋ค. ์ด๋ฌํ ๋ ๊ฐ์ง ๋์์ ๊ฐ๋ฐ ํ๊ฒฝ์์๋ง ์ ์ฉ๋ฉ๋๋ค.
- ์ด๋ฒคํธ์ ๋ฌ๋ฆฌ Effect๋ ํน์ ์ํธ์์ฉ์ด ์๋ ๋ ๋๋ง ์์ฒด์ ์ํด ๋ฐ์ํฉ๋๋ค.
- Effect๋ฅผ ์ฌ์ฉํ๋ฉด ์ปดํฌ๋ํธ๋ฅผ ์ธ๋ถ ์์คํ (ํ์ฌ API, ๋คํธ์ํฌ ๋ฑ)๊ณผ ๋๊ธฐํํ ์ ์์ต๋๋ค.
- ๊ธฐ๋ณธ์ ์ผ๋ก Effect๋ ๋ชจ๋ ๋ ๋๋ง(์ด๊ธฐ ๋ ๋๋ง ํฌํจ) ํ์ ์คํ๋ฉ๋๋ค.
- React๋ ๋ชจ๋ ์์กด์ฑ์ด ๋ง์ง๋ง ๋ ๋๋ง๊ณผ ๋์ผํ ๊ฐ์ ๊ฐ์ง๋ฉด Effect๋ฅผ ๊ฑด๋๋๋๋ค.
- ์์กด์ฑ์ "์ ํ"ํ ์ ์์ต๋๋ค. ์์กด์ฑ์ Effect ๋ด๋ถ์ ์ฝ๋์ ์ํด ๊ฒฐ์ ๋ฉ๋๋ค.
- ๋น ์์กด์ฑ ๋ฐฐ์ด(
[])์ ์ปดํฌ๋ํธ "๋ง์ดํ "(ํ๋ฉด์ ์ถ๊ฐ๋จ)์ ์๋ฏธํฉ๋๋ค. - Strict Mode์์ React๋ ์ปดํฌ๋ํธ๋ฅผ ๋ ๋ฒ ๋ง์ดํธํฉ๋๋ค(๊ฐ๋ฐ ํ๊ฒฝ์์๋ง!) ์ด๋ Effect์ ์คํธ๋ ์ค ํ ์คํธ๋ฅผ ์ํ ๊ฒ์ ๋๋ค.
- Effect๊ฐ ๋ค์ ๋ง์ดํธ๋ก ์ธํด ์ค๋จ๋ ๊ฒฝ์ฐ ํด๋ฆฐ์ ํจ์๋ฅผ ๊ตฌํํด์ผ ํฉ๋๋ค.
- React๋ Effect๊ฐ ๋ค์์ ์คํ๋๊ธฐ ์ ์ ์ ๋ฆฌ ํจ์๋ฅผ ํธ์ถํ๋ฉฐ, ๋ง์ดํธ ํด์ ์ค์๋ ํธ์ถํฉ๋๋ค.
์ด ์์์์๋ ํผ์ด <MyInput /> ์ปดํฌ๋ํธ๋ฅผ ๋ ๋๋งํฉ๋๋ค.
ํ๋ฉด์ ๋ํ๋ ๋ MyInput์ด ์๋์ผ๋ก ํฌ์ปค์ค๋๋๋ก ์
๋ ฅ์ focus() ๋ฉ์๋๋ฅผ ์ฌ์ฉํ์ธ์. ์ด๋ฏธ ์ฃผ์ ์ฒ๋ฆฌ๋ ๊ตฌํ์ด ์์ง๋ง ์ ๋๋ก ์๋ํ์ง ์์ต๋๋ค. ์ ์๋ํ์ง ์๋์ง ํ์ธํ๊ณ ์์ ํด ๋ณด์ธ์. (autoFocus ์์ฑ์ ์กด์ฌํ์ง ์๋ ๊ฒ์ผ๋ก ๊ฐ์ ํ์ธ์. ์ฐ๋ฆฌ๋ ์ฒ์๋ถํฐ ๋์ผํ ๊ธฐ๋ฅ์ ๋ค์ ๊ตฌํํ๊ณ ์์ต๋๋ค.)
import { useEffect, useRef } from 'react';
export default function MyInput({ value, onChange }) {
const ref = useRef(null);
// TODO: This doesn't quite work. Fix it.
// ref.current.focus()
return (
<input
ref={ref}
value={value}
onChange={onChange}
/>
);
}import { useState } from 'react';
import MyInput from './MyInput.js';
export default function Form() {
const [show, setShow] = useState(false);
const [name, setName] = useState('Taylor');
const [upper, setUpper] = useState(false);
return (
<>
<button onClick={() => setShow(s => !s)}>form {show ? '์จ๊ธฐ๊ธฐ' : '๋ณด๊ธฐ'}</button>
<br />
<hr />
{show && (
<>
<label>
์ด๋ฆ์ ์
๋ ฅํ์ธ์:
<MyInput
value={name}
onChange={e => setName(e.target.value)}
/>
</label>
<label>
<input
type="checkbox"
checked={upper}
onChange={e => setUpper(e.target.checked)}
/>
๋๋ฌธ์๋ก ๋ง๋ค๊ธฐ
</label>
<p>์๋
ํ์ธ์, <b>{upper ? name.toUpperCase() : name}๋</b></p>
</>
)}
</>
);
}label {
display: block;
margin-top: 20px;
margin-bottom: 20px;
}
body {
min-height: 150px;
}์๋ฃจ์ ์ด ์ ๋๋ก ์๋ํ๋์ง ํ์ธํ๋ ค๋ฉด "form ๋ณด๊ธฐ"๋ฅผ ๋๋ฅด๊ณ ์ ๋ ฅ๋์ด ํฌ์ปค์ค๋๋์ง ํ์ธํ์ธ์.(๊ฐ์กฐ ํ์, ์ปค์๊ฐ ๋ด๋ถ์ ๋ฐฐ์น๋จ). "form ์จ๊ธฐ๊ธฐ"๋ฅผ ๋๋ฅด๊ณ ๋ค์ "form ๋ณด๊ธฐ"๋ฅผ ๋๋ฌ ์ ๋ ฅ๋์ด ๋ค์ ๊ฐ์กฐ ํ์๋๋์ง ํ์ธํ์ธ์.
MyInput์ ๋ ๋๋ง ํ ๋งค๋ฒ ํฌ์ปค์ค๋๋ ๊ฒ์ด ์๋๋ผ ๋ง์ดํธ ์์๋ง ํฌ์ปค์ค๋์ด์ผ ํฉ๋๋ค. ์ด ๋์์ด ์ฌ๋ฐ๋ฅธ์ง ํ์ธํ๋ ค๋ฉด "form ๋ณด๊ธฐ"๋ฅผ ๋๋ฅธ ๋ค์ "๋๋ฌธ์๋ก ๋ง๋ค๊ธฐ" ์ฒดํฌ๋ฐ์ค๋ฅผ ๋ฐ๋ณตํด์ ํด๋ฆญํ์ธ์. ์ฒดํฌ๋ฐ์ค๋ฅผ ํด๋ฆญํด๋ ์๋จ์ ์
๋ ฅ๋์ ํฌ์ปค์ค๊ฐ ๋์ง ์์์ผ ํฉ๋๋ค.
๋ ๋๋ง ์ค์ ref.current.focus()๋ฅผ ํธ์ถํ๋ ๊ฒ์ ์ ์ ํ์ง ์์ต๋๋ค. ๋ถ์ ํจ๊ณผ์ด๊ธฐ ๋๋ฌธ์
๋๋ค. ๋ถ์ ํจ๊ณผ๋ ์ด๋ฒคํธ ํธ๋ค๋ฌ ๋ด๋ถ์ ๋ฐฐ์นํ๊ฑฐ๋ useEffect๋ก ์ ์ธํด์ผ ํฉ๋๋ค. ์ด ๊ฒฝ์ฐ์๋ ๋ถ์์ฉ์ด ํน์ ์ํธ์์ฉ์ด ์๋๋ผ ์ปดํฌ๋ํธ๊ฐ ๋ํ๋๋ ๊ฒ์ ์ํด ๋ฐ์๋๊ธฐ ๋๋ฌธ์ Effect ๋ด๋ถ์ ๋ฃ๋ ๊ฒ์ด ๋ง์ต๋๋ค.
์ค์๋ฅผ ๊ณ ์น๋ ค๋ฉด ref.current.focus() ํธ์ถ์ Effect ์ ์ธ์ผ๋ก ๊ฐ์ธ์ธ์. ๊ทธ๋ฐ ๋ค์, ์ด Effect๊ฐ ๋ ๋๋ง ํ ๋งค๋ฒ ์คํ๋๋ ๊ฒ์ด ์๋๋ผ ๋ง์ดํธ ์์๋ง ์คํ๋๋๋ก ํ๋ ค๋ฉด ๋น [] ์์กด์ฑ์ ์ถ๊ฐํ์ธ์.
import { useEffect, useRef } from 'react';
export default function MyInput({ value, onChange }) {
const ref = useRef(null);
useEffect(() => {
ref.current.focus();
}, []);
return (
<input
ref={ref}
value={value}
onChange={onChange}
/>
);
}import { useState } from 'react';
import MyInput from './MyInput.js';
export default function Form() {
const [show, setShow] = useState(false);
const [name, setName] = useState('Taylor');
const [upper, setUpper] = useState(false);
return (
<>
<button onClick={() => setShow(s => !s)}>form {show ? '์จ๊ธฐ๊ธฐ' : '๋ณด๊ธฐ'} form</button>
<br />
<hr />
{show && (
<>
<label>
์ด๋ฆ์ ์
๋ ฅํ์ธ์:
<MyInput
value={name}
onChange={e => setName(e.target.value)}
/>
</label>
<label>
<input
type="checkbox"
checked={upper}
onChange={e => setUpper(e.target.checked)}
/>
๋๋ฌธ์๋ก ๋ง๋ค๊ธฐ
</label>
<p>์๋
ํ์ธ์, <b>{upper ? name.toUpperCase() : name}</b>๋</p>
</>
)}
</>
);
}label {
display: block;
margin-top: 20px;
margin-bottom: 20px;
}
body {
min-height: 150px;
}์ด ํผ์ ๋ ๊ฐ์ <MyInput /> ์ปดํฌ๋ํธ๋ฅผ ๋ ๋๋งํฉ๋๋ค.
"form ๋ณด๊ธฐ"๋ฅผ ๋๋ฅด๋ฉด ๋ ๋ฒ์งธ ํ๋๊ฐ ์๋์ผ๋ก ํฌ์ปค์ค๋ฉ๋๋ค. ์ด๋ ๋ <MyInput /> ์ปดํฌ๋ํธ ๋ชจ๋ ๋ด๋ถ์ ํ๋์ ํฌ์ปค์ค๋ฅผ ์ฃผ๋ ค๊ณ ํ๊ธฐ ๋๋ฌธ์
๋๋ค. ๋ ๊ฐ์ ์
๋ ฅ ํ๋์ ์ฐ์ํด์ focus()๋ฅผ ํธ์ถํ๋ฉด ๋ง์ง๋ง ํธ์ถ์ด ํญ์ "์น๋ฆฌํ๊ฒ" ๋ฉ๋๋ค.
์ด์ ์ฒซ ๋ฒ์งธ ํ๋์ ํฌ์ปค์ค๋ฅผ ์ฃผ๋ ค๋ฉด ์ฒซ ๋ฒ์งธ MyInput ์ปดํฌ๋ํธ๊ฐ true๋ก ์ค์ ๋ shouldFocus prop์ ๋ฐ๋๋ก ๋ณ๊ฒฝํด์ผ ํฉ๋๋ค. ๋ณ๊ฒฝ๋ ๋ก์ง์ ๋ฐ๋ผ MyInput์ด ๋ฐ์ shouldFocus prop์ด true์ผ ๋์๋ง focus()๊ฐ ํธ์ถ๋๋๋ก ๋ณ๊ฒฝํด ๋ณด์ธ์.
import { useEffect, useRef } from 'react';
export default function MyInput({ shouldFocus, value, onChange }) {
const ref = useRef(null);
// TODO: shouldFocus๊ฐ true์ผ๋๋ง ํธ์ถ๋๋๋ก
useEffect(() => {
ref.current.focus();
}, []);
return (
<input
ref={ref}
value={value}
onChange={onChange}
/>
);
}import { useState } from 'react';
import MyInput from './MyInput.js';
export default function Form() {
const [show, setShow] = useState(false);
const [firstName, setFirstName] = useState('Taylor');
const [lastName, setLastName] = useState('Swift');
const [upper, setUpper] = useState(false);
const name = firstName + ' ' + lastName;
return (
<>
<button onClick={() => setShow(s => !s)}>form {show ? '์จ๊ธฐ๊ธฐ' : '๋ณด๊ธฐ'}</button>
<br />
<hr />
{show && (
<>
<label>
์ด๋ฆ์ ์
๋ ฅํ์ธ์:
<MyInput
value={firstName}
onChange={e => setFirstName(e.target.value)}
shouldFocus={true}
/>
</label>
<label>
์ฑ์ ์
๋ ฅํ์ธ์:
<MyInput
value={lastName}
onChange={e => setLastName(e.target.value)}
shouldFocus={false}
/>
</label>
<p>์๋
ํ์ธ์, <b>{upper ? name.toUpperCase() : name}</b>๋</p>
</>
)}
</>
);
}label {
display: block;
margin-top: 20px;
margin-bottom: 20px;
}
body {
min-height: 150px;
}ํด๋น ์ฝ๋๋ฅผ ์คํํ๊ณ ์ฃผ์ด์ง ๊ฒ์ฆ ๋ฐฉ๋ฒ์ ๋ฐ๋ผ ์งํํด ๋ด
์๋ค. "form ๋ณด๊ธฐ" ๋ฒํผ์ ๋ฐ๋ณต์ ์ผ๋ก ๋๋ฅด๊ณ "form ์จ๊ธฐ๊ธฐ" ๋ฒํผ์ ํด๋ฆญํ์ฌ ๊ฒฐ๊ณผ๋ฅผ ํ์ธํ ์ ์์ต๋๋ค. ํผ์ด ๋ํ๋ ๋, ์ฒซ ๋ฒ์งธ ์
๋ ฅ ํ๋์๋ง ํฌ์ปค์ค๊ฐ ์ค์ ๋ฉ๋๋ค. ๋ถ๋ชจ ์ปดํฌ๋ํธ๊ฐ ์ฒซ ๋ฒ์งธ ์
๋ ฅ ํ๋๋ฅผ shouldFocus={true}๋ก ๋ ๋๋งํ๊ณ ๋ ๋ฒ์งธ ์
๋ ฅ ํ๋๋ฅผ shouldFocus={false}๋ก ๋ ๋๋งํ๊ธฐ ๋๋ฌธ์
๋๋ค. ๋ํ ๋ ์
๋ ฅ ํ๋ ๋ชจ๋ ์ ์์ ์ผ๋ก ์๋ํ๋ฉฐ, ๋ ๋ค ํ
์คํธ๋ฅผ ์
๋ ฅํ ์ ์์ต๋๋ค.
์กฐ๊ฑด๋ถ๋ก useEffect๋ฅผ ์ ์ธํ ์๋ ์์ง๋ง, useEffect ๋ด๋ถ์ ์กฐ๊ฑด๋ถ ๋ก์ง์ ํฌํจ์์ผ ์ํ๋ ๋์์ ๊ตฌํํ ์ ์์ต๋๋ค.
์กฐ๊ฑด๋ถ ๋ก์ง์ Effect ๋ด๋ถ๋ก ๋ฃ์ด์ฃผ์ธ์. shouldFocus๋ฅผ Effect ๋ด์์ ์ฌ์ฉํ๋ฏ๋ก ์ด๋ฅผ ์์กด์ฑ์ผ๋ก ๋ช
์ํด์ผ ํฉ๋๋ค. (๋ง์ฝ ์ด๋ค input์ shouldFocus๊ฐ false์์ true๋ก ๋ณ๊ฒฝ๋๋ค๋ฉด, ๋ง์ดํธ ํ์ ํฌ์ปค์ค๊ฐ ๋ ๊ฒ์
๋๋ค.)
import { useEffect, useRef } from 'react';
export default function MyInput({ shouldFocus, value, onChange }) {
const ref = useRef(null);
useEffect(() => {
if (shouldFocus) {
ref.current.focus();
}
}, [shouldFocus]);
return (
<input
ref={ref}
value={value}
onChange={onChange}
/>
);
}import { useState } from 'react';
import MyInput from './MyInput.js';
export default function Form() {
const [show, setShow] = useState(false);
const [firstName, setFirstName] = useState('Taylor');
const [lastName, setLastName] = useState('Swift');
const [upper, setUpper] = useState(false);
const name = firstName + ' ' + lastName;
return (
<>
<button onClick={() => setShow(s => !s)}>form {show ? '์จ๊ธฐ๊ธฐ' : '๋ณด๊ธฐ'}</button>
<br />
<hr />
{show && (
<>
<label>
์ด๋ฆ์ ์
๋ ฅํ์ธ์:
<MyInput
value={firstName}
onChange={e => setFirstName(e.target.value)}
shouldFocus={true}
/>
</label>
<label>
์ฑ์ ์
๋ ฅํ์ธ์:
<MyInput
value={lastName}
onChange={e => setLastName(e.target.value)}
shouldFocus={false}
/>
</label>
<p>์๋
ํ์ธ์, <b>{upper ? name.toUpperCase() : name}๋</b></p>
</>
)}
</>
);
}label {
display: block;
margin-top: 20px;
margin-bottom: 20px;
}
body {
min-height: 150px;
}์๋ Counter ์ปดํฌ๋ํธ๋ ๋งค ์ด๋ง๋ค ์ฆ๊ฐํ๋ ์นด์ดํฐ๋ฅผ ๋ํ๋
๋๋ค. ์ปดํฌ๋ํธ๊ฐ ๋ง์ดํธ๋ ๋ setInterval์ ํธ์ถํฉ๋๋ค. ์ด๋ก ์ธํด onTick ํจ์๊ฐ ๋งค ์ด๋ง๋ค ์คํ๋ฉ๋๋ค. onTick ํจ์๋ ์นด์ดํฐ๋ฅผ ์ฆ๊ฐ์ํต๋๋ค.
ํ์ง๋ง 1์ด๋ง๋ค ํ ๋ฒ์ฉ ์ฆ๊ฐํ๋ ๋์ ๋ ๋ฒ์ฉ ์ฆ๊ฐํฉ๋๋ค. ์ ๊ทธ๋ด๊น์? ๋ฒ๊ทธ์ ์์ธ์ ์ฐพ์ ์์ ํ์ธ์.
setInterval์ interval ID๋ฅผ ๋ฐํํ๋๋ฐ, ์ด๋ฅผ clearInterval ํจ์์ ์ ๋ฌํ์ฌ interval์ ์ค์งํ ์ ์์ต๋๋ค.
import { useState, useEffect } from 'react';
export default function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
function onTick() {
setCount(c => c + 1);
}
setInterval(onTick, 1000);
}, []);
return <h1>{count}</h1>;
}import { useState } from 'react';
import Counter from './Counter.js';
export default function Form() {
const [show, setShow] = useState(false);
return (
<>
<button onClick={() => setShow(s => !s)}>์นด์ดํฐ {show ? '์จ๊ธฐ๊ธฐ' : '๋ณด๊ธฐ'}</button>
<br />
<hr />
{show && <Counter />}
</>
);
}label {
display: block;
margin-top: 20px;
margin-bottom: 20px;
}
body {
min-height: 150px;
}Strict Mode๊ฐ ํ์ฑํ๋ ๊ฒฝ์ฐ (์ด ์ฌ์ดํธ์ ์ฝ๋ ์์ ์๋๋ฐ์ค์ฒ๋ผ), React๋ ๊ฐ๋ฐ ์ค์ ๊ฐ ์ปดํฌ๋ํธ๋ฅผ ํ ๋ฒ์ฉ ๋ฆฌ๋ง์ดํธํฉ๋๋ค. ์ด๋ก ์ธํด ๊ฐ๊ฒฉ์ด ๋ ๋ฒ ์ค์ ๋์ด ๋งค ์ด๋ง๋ค ์นด์ดํฐ๊ฐ ๋ ๋ฒ ์ฆ๊ฐํฉ๋๋ค.
๊ทธ๋ฌ๋ React์ ๋์์ด ๋ฒ๊ทธ์ ์์ธ์ ์๋๋๋ค. ๋ฒ๊ทธ๋ ์ฝ๋์ ์์ต๋๋ค. React์ ๋์์ ๋ฒ๊ทธ๋ฅผ ๋ ๋์ ๋๊ฒ ๋ง๋ญ๋๋ค. ์ค์ ๋ฌธ์ ๋ ์ด Effect๊ฐ ํ๋ก์ธ์ค๋ฅผ ์์ํ ํ์ ํด๋ฆฐ์ ํ ์ ์๋ ๋ฐฉ๋ฒ์ ์ ๊ณตํ์ง ์๋ ๊ฒ์ ๋๋ค.
์ด ์ฝ๋๋ฅผ ์์ ํ๋ ค๋ฉด setInterval์ ์ํด ๋ฐํ๋ interval ID๋ฅผ ์ ์ฅํ๊ณ , clearInterval์ ์ฌ์ฉํ์ฌ ํด๋ฆฐ์
ํจ์๋ฅผ ๊ตฌํํ์ธ์.
import { useState, useEffect } from 'react';
export default function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
function onTick() {
setCount(c => c + 1);
}
const intervalId = setInterval(onTick, 1000);
return () => clearInterval(intervalId);
}, []);
return <h1>{count}</h1>;
}import { useState } from 'react';
import Counter from './Counter.js';
export default function App() {
const [show, setShow] = useState(false);
return (
<>
<button onClick={() => setShow(s => !s)}>์นด์ดํฐ {show ? '์จ๊ธฐ๊ธฐ' : '๋ณด๊ธฐ'}</button>
<br />
<hr />
{show && <Counter />}
</>
);
}label {
display: block;
margin-top: 20px;
margin-bottom: 20px;
}
body {
min-height: 150px;
}๊ฐ๋ฐ ์ค์ React๋ ์ฌ์ ํ ์ปดํฌ๋ํธ๋ฅผ ํ ๋ฒ ๋ฆฌ๋ง์ดํธํ์ฌ ํด๋ฆฐ์
์ด ์ ๊ตฌํ๋์๋์ง ํ์ธํฉ๋๋ค. ๋ฐ๋ผ์ ์ต์ด์ setInterval ํธ์ถ ์ดํ์, ๋ฐ๋ก ๋ค์ clearInterval, ๊ทธ๋ฆฌ๊ณ ๋ค์ setInterval ํธ์ถ์ด ๋ฐ์ํฉ๋๋ค. ํ๋ก๋์
(์ด์ ํ๊ฒฝ)์์๋ setInterval ํธ์ถ์ด ํ ๋ฒ๋ง ์์ ๊ฒ์
๋๋ค. ๊ฐ๋ฐ ํ๊ฒฝ๊ณผ ์ด์ ํ๊ฒฝ ๋ชจ๋ ์ฌ์ฉ์๊ฐ ๋ณผ ์ ์๋ ๋์์ ๋์ผํฉ๋๋ค. ์นด์ดํฐ๊ฐ 1์ด๋ง๋ค ํ ๋ฒ์ฉ ์ฆ๊ฐํ๋ ๊ฒ์ด์ฃ .
์ด ์ปดํฌ๋ํธ๋ select ํ๊ทธ๋ก ์ ํํ ์ฌ๋์ ์ผ๋๊ธฐ๋ฅผ ๋ณด์ฌ์ค๋๋ค. ์ด ์ปดํฌ๋ํธ๋ ์ ํ๋ person์ด ๋ณ๊ฒฝ๋ ๋๋ง๋ค, ๋ํ ๋ง์ดํธ๋ ๋๋ง๋ค ๋น๋๊ธฐ ํจ์ fetchBio(person)๋ฅผ ํธ์ถํ์ฌ ์ผ๋๊ธฐ๋ฅผ ๋ถ๋ฌ์ต๋๋ค. ์ด ๋น๋๊ธฐ ํจ์๋ Promise๋ฅผ ๋ฐํํ๋ฉฐ, ์ด Promise๋ ๊ฒฐ๊ตญ ๋ฌธ์์ด๋ก resolve๋ฉ๋๋ค. ๋ถ๋ฌ์ค๊ธฐ๊ฐ ์๋ฃ๋๋ฉด setBio๋ฅผ ํธ์ถํ์ฌ ํด๋น ๋ฌธ์์ด์ select์ option์ผ๋ก ํ์ํฉ๋๋ค.
{/* not the most efficient, but this validation is enabled in the linter only, so it's fine to ignore it here since we know what we're doing */}
import { useState, useEffect } from 'react';
import { fetchBio } from './api.js';
export default function Page() {
const [person, setPerson] = useState('Alice');
const [bio, setBio] = useState(null);
useEffect(() => {
setBio(null);
fetchBio(person).then(result => {
setBio(result);
});
}, [person]);
return (
<>
<select value={person} onChange={e => {
setPerson(e.target.value);
}}>
<option value="Alice">Alice</option>
<option value="Bob">Bob</option>
<option value="Taylor">Taylor</option>
</select>
<hr />
<p><i>{bio ?? 'Loading...'}</i></p>
</>
);
}export async function fetchBio(person) {
const delay = person === 'Bob' ? 2000 : 200;
return new Promise(resolve => {
setTimeout(() => {
resolve('์ด๊ฒ์ ' + person + '์ ์ผ๋๊ธฐ์
๋๋ค.');
}, delay);
})
}์ด ์ฝ๋์ ๋ฒ๊ทธ๊ฐ ์์ต๋๋ค. "Alice"๋ฅผ ์ ํํ ๋ค์ "Bob"์ ์ ํํ ๋ค์ ๋ฐ๋ก "Taylor"์ ์ ํํ๋ฉด ๋ฒ๊ทธ๊ฐ ๋ฐ์ํฉ๋๋ค. ์ถฉ๋ถํ ๋น ๋ฅด๊ฒ ์ด ์์ ์ ์ํํ๋ฉด ๋ฒ๊ทธ๋ฅผ ํ์ธํ ์ ์์ต๋๋ค. Taylor๊ฐ ์ ํ๋์์ง๋ง ์๋ ๋จ๋ฝ์๋ "์ด๊ฒ์ Bob์ ์ผ๋๊ธฐ์ ๋๋ค."๋ผ๊ณ ํ์๋ฉ๋๋ค.
์ด๋ฌํ ํ์์ด ๋ฐ์ํ๋ ์ด์ ๋ ๋ฌด์์ผ๊น์? ์ด Effect ๋ด๋ถ์ ๋ฒ๊ทธ๋ฅผ ์์ ํ์ธ์.
Effect๊ฐ ๋น๋๊ธฐ๋ก ๋ฌด์ธ๊ฐ๋ฅผ ๊ฐ์ ธ์ค๋ ๊ฒฝ์ฐ ์ผ๋ฐ์ ์ผ๋ก ํด๋ฆฐ์ ์ด ํ์ํฉ๋๋ค.
๋ฒ๊ทธ๋ฅผ ํธ๋ฆฌ๊ฑฐํ๋ ค๋ฉด ๋ค์ ์์๋๋ก ์งํ๋์ด์ผ ํฉ๋๋ค:
'Bob'์ ์ ํํ๋ฉดfetchBio('Bob')๊ฐ ํธ๋ฆฌ๊ฑฐ๋ฉ๋๋ค.'Taylor'์ ์ ํํ๋ฉดfetchBio('Taylor')๊ฐ ํธ๋ฆฌ๊ฑฐ๋ฉ๋๋ค.'Taylor'์ ์ผ๋๊ธฐ๋ฅผ ๊ฐ์ ธ์ค๋ ์์ ์ด'Bob'์ ์ผ๋๊ธฐ๋ฅผ ๊ฐ์ ธ์ค๋ ์์ ๋ณด๋ค ๋จผ์ ์๋ฃ๋ฉ๋๋ค.'Taylor'๋ ๋๋ง์ Effect๊ฐsetBio('This is Taylorโs bio')๋ฅผ ํธ์ถํฉ๋๋ค.'Bob'์ ์ผ๋๊ธฐ๋ฅผ ๊ฐ์ ธ์ค๋ ์์ ์ด ์๋ฃ๋ฉ๋๋ค.'Bob'๋ ๋๋ง์ Effect๊ฐsetBio('This is Bobโs bio')๋ฅผ ํธ์ถํฉ๋๋ค.
์ด๋ ๊ฒ ํ๋ฉด Taylor๊ฐ ์ ํ๋์์์๋ ๋ถ๊ตฌํ๊ณ Bob์ ์ผ๋๊ธฐ๊ฐ ํ์๋ฉ๋๋ค. ์ด์ ๊ฐ์ ๋ฒ๊ทธ๋ ๋ ๊ฐ์ ๋น๋๊ธฐ ์์ ์ด "๊ฒฝ์(race)"ํ๋ฉฐ ์์ ์๋ฃ์ ์์๋ฅผ ์์ํ ์ ์๋ ๊ฒฝ์ ์กฐ๊ฑด(race condition)์ด๋ผ๊ณ ํฉ๋๋ค.
์ด ๊ฒฝ์ ์กฐ๊ฑด์ ํด๊ฒฐํ๋ ค๋ฉด ํด๋ฆฐ์ ํจ์๋ฅผ ์ถ๊ฐํ์ธ์.
{/* not the most efficient, but this validation is enabled in the linter only, so it's fine to ignore it here since we know what we're doing */}
import { useState, useEffect } from 'react';
import { fetchBio } from './api.js';
export default function Page() {
const [person, setPerson] = useState('Alice');
const [bio, setBio] = useState(null);
useEffect(() => {
let ignore = false;
setBio(null);
fetchBio(person).then(result => {
if (!ignore) {
setBio(result);
}
});
return () => {
ignore = true;
}
}, [person]);
return (
<>
<select value={person} onChange={e => {
setPerson(e.target.value);
}}>
<option value="Alice">Alice</option>
<option value="Bob">Bob</option>
<option value="Taylor">Taylor</option>
</select>
<hr />
<p><i>{bio ?? 'Loading...'}</i></p>
</>
);
}export async function fetchBio(person) {
const delay = person === 'Bob' ? 2000 : 200;
return new Promise(resolve => {
setTimeout(() => {
resolve('์ด๊ฒ์ ' + person + '์ ์ผ๋๊ธฐ์
๋๋ค.');
}, delay);
})
}๊ฐ ๋ ๋๋ง์ Effect๋ ์์ฒด ignore ๋ณ์๋ฅผ ๊ฐ์ง๊ณ ์์ต๋๋ค. ์ฒ์์ ignore ๋ณ์๋ false๋ก ์ค์ ๋ฉ๋๋ค. ๊ทธ๋ฌ๋ Effect๊ฐ ํด๋ฆฐ์
๋๋ฉด(์: ๋ค๋ฅธ ์ฌ๋์ ์ ํํ ๋), ํด๋น Effect์ ignore ๋ณ์๋ true๋ก ์ค์ ๋ฉ๋๋ค. ์ด์ ์ด๋ค ์์๋ก ์์ฒญ์ด ์๋ฃ๋๋์ง๋ ์ค์ํ์ง ์์ต๋๋ค. ๋ง์ง๋ง ์ฌ๋์ Effect๋ง ignore๊ฐ false๋ก ์ค์ ๋๋ฏ๋ก setBio(result)๋ฅผ ํธ์ถํฉ๋๋ค. ์ด์ Effect๋ ์ ๋ฆฌ๋์์ผ๋ฏ๋ก if (!ignore) ๊ฒ์ฌ๊ฐ setBio ํธ์ถ์ ๋ฐฉ์งํฉ๋๋ค:
'Bob'์ ์ ํํ๋ฉดfetchBio('Bob')๊ฐ ํธ๋ฆฌ๊ฑฐ๋ฉ๋๋ค.'Taylor'์ ์ ํํ๋ฉดfetchBio('Taylor')๊ฐ ํธ๋ฆฌ๊ฑฐ๋๋ฉฐ ์ด์ (Bob์) Effect๊ฐ **์ ๋ฆฌ(cleaned up)**๋ฉ๋๋ค.'Taylor'์ ์ผ๋๊ธฐ๋ฅผ ๊ฐ์ ธ์ค๋ ์์ ์ด'Bob'์ ์ผ๋๊ธฐ๋ฅผ ๊ฐ์ ธ์ค๋ ์์ ๋ณด๋ค ๋จผ์ ์๋ฃ๋ฉ๋๋ค.'Taylor'๋ ๋๋ง์ Effect๊ฐsetBio('This is Taylorโs bio')๋ฅผ ํธ์ถํฉ๋๋ค.'Bob'์ ์ผ๋๊ธฐ๋ฅผ ๊ฐ์ ธ์ค๋ ์์ ์ด ์๋ฃ๋ฉ๋๋ค.'Bob'๋ ๋๋ง์ Effect๋ignoreํ๋๊ทธ๊ฐtrue๋ก ์ค์ ๋์๊ธฐ ๋๋ฌธ์ ์๋ฌด ์ผ๋ ์ํํ์ง ์์ต๋๋ค.
์ค๋๋ API ํธ์ถ์ ๊ฒฐ๊ณผ๋ฅผ ๋ฌด์ํ๋ ๊ฒ ์ธ์๋ ๋ ์ด์ ํ์ํ์ง ์์ ์์ฒญ์ ์ทจ์ํ๊ธฐ ์ํด AbortController๋ฅผ ์ฌ์ฉํ ์๋ ์์ต๋๋ค. ๊ทธ๋ฌ๋ ์ด๊ฒ๋ง์ผ๋ก๋ ๊ฒฝ์ ์กฐ๊ฑด์ ๋ํ ์ถฉ๋ถํ ๋ณดํธ๊ฐ ์ด๋ค์ง์ง ์์ต๋๋ค. ํผ์น ๋ชปํ ์ํฉ์์๋ ์ถ๊ฐ์ ์ธ ๋น๋๊ธฐ ์์
์ด ํํํ ์ ์์ผ๋ฏ๋ก ignore์ ๊ฐ์ ๋ช
์์ ํ๋๊ทธ๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ด ์ด๋ฌํ ์ข
๋ฅ์ ๋ฌธ์ ๋ฅผ ๊ฐ์ฅ ์์ ํ๊ฒ ํด๊ฒฐํ๋ ๊ฐ์ฅ ์ ๋ขฐํ ์ ์๋ ๋ฐฉ๋ฒ์
๋๋ค.