hooks-useEffect

โ“What is useEffect

  • ๋ Œ๋”๋ง ํ›„ ์ผ๋ถ€ ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ํ•˜์—ฌ ์ปดํฌ๋„ŒํŠธ์— ์ด๋ฒคํŠธ๋ฅผ ๊ฑธ๊ฑฐ๋‚˜ ์™ธ๋ถ€ ์‹œ์Šคํ…œ๊ณผ React ๋™๊ธฐํ™”

    • ํŠน์ • ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋งˆ์šดํŠธ/์–ธ๋งˆ์šดํŠธ ๋  ๋•Œ ์ฑ„ํŒ… ์„œ๋ฒ„ ์—ฐ๊ฒฐ/์ค‘์ง€

    • ํŠน์ • ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋งˆ์šดํŠธ ๋  ๋•Œ ์„œ๋ฒ„์—์„œ ๋ฐ์ดํ„ฐ ๊ฐ€์ ธ์˜ค๊ธฐ

    • ํŠน์ • ์ปดํฌ๋„ŒํŠธ๊ฐ€ ํ™”๋ฉด์— ํ‘œ์‹œ๋  ๋•Œ ๋ถ„์„ ๋กœ๊ทธ ์ „์†ก

  • ๊ธฐ๋ณธ์ ์œผ๋กœ ๋ Œ๋”๋ง ๋˜๋Š” ๋ฆฌ๋ Œ๋”๋ง ํ•  ๋•Œ๋งˆ๋‹ค ์‹คํ–‰

  • ๋ Œ๋”๋ง ๋  ๋•Œ React ๋Š” ํ™”๋ฉด์„ ์—…๋ฐ์ดํŠธ ํ•œ ํ›„์—, useEffect ๋‚ด๋ถ€์˜ ์ฝ”๋“œ๋ฅผ ์‹คํ–‰

  • ์ฆ‰, ๋ Œ๋”๋ง์ด ๋ธŒ๋ผ์šฐ์ €์— ๋ฐ˜์˜๋  ๋•Œ ๊นŒ์ง€, useEffect์˜ ์‹คํ–‰์€ ์ง€์—ฐ

โ“๋ Œ๋”๋ง์ด๋ž€?

  • ์ปดํฌ๋„ŒํŠธ์˜ ์ตœ์ƒ์œ„ ๋ ˆ๋ฒจ

  • ๊ฐ’ ๋˜๋Š” ์ƒํƒœ๋ฅผ ๊ฐ€์ ธ์™€์„œ ๋ฐ˜ํ™˜ ํ›„, ํ™”๋ฉด์— ํ‘œ์‹œํ•  JSX๋ฅผ ๋ฐ˜ํ™˜

  • ๋ Œ๋”๋ง ์ฝ”๋“œ๋Š” ์ˆœ์ˆ˜ํ•˜๊ฒŒ ๊ฒฐ๊ณผ๋งŒ ๊ณ„์†ํ•˜๊ณ  ๋‹ค๋ฅธ ์ž‘์—…์€ ์ˆ˜ํ–‰ํ•˜์ง€ ์•Š์•„์•ผ ํ•จ

๐Ÿ’ ์‚ฌ์šฉ๋ฐฉ๋ฒ•: ๊ธฐ๋ณธ ์ฝ”๋“œ ๋ฐ ์ฃผ์˜ ์‚ฌํ•ญ

์•„๋ž˜์˜ ์ฝ”๋“œ๋Š” useEffect ์˜ ๊ธฐ๋ณธ ์ฝ”๋“œ์ด๋‹ค.

import { useEffect } from 'react';

function MyComponent() {
  useEffect(() => {
    // Code here will run after _every_ render
  });

  return <div />;
}

๋‹ค์Œ์˜ ์ฝ”๋“œ๋Š” ์—๋Ÿฌ๋ฅผ ๋ฐœ์ƒ์‹œํ‚จ๋‹ค. ๋ Œ๋”๋ง ์ค‘ DOM ๋…ธ๋“œ ์ˆ˜์ •๊ณผ ๊ฐ™์€ ์ž‘์—…์€ ํ•˜๋ฉด ์•ˆ๋œ๋‹ค.

ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•์€ ๋ Œ๋”๋ง์ด ๋๋‚œ ํ›„ DOM ์กฐ์ž‘์„ ํ•ด์ฃผ๋ฉด ๋˜๋Š” ๊ฒƒ์ธ๋ฐ useEffect ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋œ๋‹ค. DOM ์—…๋ฐ์ดํŠธ๋ฅผ useEffect ๋กœ ๊ฐ์‹ธ๋ฉด React๋Š” ํ™”๋ฉด์„ ๋จผ์ € ์—…๋ฐ์ดํŠธ ํ•˜๊ณ ๋‚˜์„œ useEffect ๋‚ด๋ถ€์˜ ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ํ•œ๋‹ค.

import { useState, useRef, useEffect } from 'react';

function VideoPlayer({ src, isPlaying }) {
  const ref = useRef(null);

  if (isPlaying) {
    ref.current.play(); // Calling these while rendering isn't allowed.
  } else {
    ref.current.pause(); // Also, this crashes.
  }

  return <video ref={ref} src={src} loop playsInline />;
}

export default function App() {
  const [isPlaying, setIsPlaying] = useState(false);
  return (
    <>
      <button
        onClick={() => {
          setIsPlaying(!isPlaying);
        }}
      >
        {isPlaying ? 'Pause' : 'Play'}
      </button>
      <VideoPlayer
        isPlaying={isPlaying}
        src='https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4'
      />
    </>
  );
}
img-useEffect_error01

๐Ÿ’ ์ข…์†์„ฑ ์ง€์ •

  • ๊ธฐ๋ณธ์ ์œผ๋กœ useEffect๋Š” ๋ชจ๋“  ๋ Œ๋”๋ง ํ›„์— ์‹คํ–‰๋œ๋‹ค.

  • ๊ฐ™์€ ์ปดํฌ๋„ŒํŠธ ๋‚ด์—์„œ ์–ด๋–ค ์ƒํƒœ ๊ฐ’์ด ๋ณ€๊ฒฝ๋˜๋ฉด ์ปดํฌ๋„ŒํŠธ๋Š” ๋ฆฌ๋ Œ๋”๋ง์ด ๋œ๋‹ค.

  • ์ดํŽ™ํŠธ ๋‚ด๋ถ€์˜ ํ•จ์ˆ˜๋Š” ๊ณ„์†ํ•ด์„œ ํ˜ธ์ถœ๋œ๋‹ค.

  • ์ด๋Ÿฐ ๊ฒฝ์šฐ์™€ ๊ฐ™์ด ๋งค๋ฒˆ ์‹คํ–‰๋˜๊ธธ ์›ํ•˜์ง€ ์•Š๋Š” ๊ฒฝ์šฐ๊ฐ€ ๋” ๋งŽ๋‹ค.

  • ์ด ๋–„ ์ข…์†์„ฑ์„ ์‚ฌ์šฉํ•œ๋‹ค. ์ข…์†์„ฑ์€ ์ง€์ •ํ•  ์ˆ˜ ์žˆ๊ณ , ๊ธฐ๋ณธ ๋ฐฐ์—ด([]) ๊ฐ’์„ ์„ค์ •ํ•  ์ˆ˜๋„ ์žˆ๋‹ค.

์•„๋ž˜์˜ ์˜ˆ์ œ๋Š” ์ข…์†์„ฑ์œผ๋กœ ๋นˆ ๋ฐฐ์—ด([])์„ ์ง€์ •ํ•˜์˜€๋‹ค. ๋”ฐ๋ผ์„œ, console.log('test')๋Š” ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์˜ค์ง ์ฒ˜์Œ ๋งˆ์šดํŠธ ๋  ๋•Œ์—๋งŒ ์‹คํ–‰๋œ๋‹ค.

import { useState, useEffect } from 'react';

export default function App() {
  const [count, setCount] = useState < number > 0;

  useEffect(() => {
    console.log('test');
  }, []);

  return (
    <>
      <button
        onClick={() => {
          setCount(count + 1);
        }}
      >
        Increase - {count}
      </button>
    </>
  );
}

  • ์•„๋ž˜์˜ ์ฝ”๋“œ๋Š” ๋ฒ„ํŠผ์„ ํด๋ฆญํ–ˆ์„ ๋•Œ ์•„๋ฌด๋Ÿฐ ๋™์ž‘์ด ๋ฐœ์ƒํ•˜์ง€ ์•Š๋Š”๋‹ค.

  • ๋ฌธ์ œ๋Š” ์ดํŽ™ํŠธ ๋‚ด๋ถ€์˜ ์ฝ”๋“œ๊ฐ€ isPlaying ๊ฐ’์— ์˜์กดํ•˜์—ฌ ์ˆ˜ํ–‰ํ•  ์ž‘์—…์„ ๊ฒฐ์ •ํ•˜๋Š”๋ฐ, ์ด ๊ฐ’์ด ์˜์กด์„ฑ๋ฐฐ์—ด์— ์—†๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

  • ์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๋ ค๋ฉด ์ข…์†์„ฑ ๋ฐฐ์—ด์— isPlaying ์„ ์ถ”๊ฐ€ํ•˜๋ฉด ๋œ๋‹ค.

  • ์˜์กด์„ฑ ๋ฐฐ์—ด์—๋Š” ์—ฌ๋Ÿฌ ๊ฐœ์˜ ๊ฐ’์ด ๋“ค์–ด๊ฐˆ ์ˆ˜ ์žˆ๋‹ค.

  • React๋Š” ์ง€์ •ํ•œ ๋ชจ๋“  ์ข…์†์„ฑ์˜ ๊ฐ’์ด ์ด์ „ ๋ Œ๋”๋ง ์‹œ์™€ ์ •ํ™•ํžˆ ๋™์ผํ•œ ๊ฒฝ์šฐ์—๋งŒ, ์ดํŽ™ํŠธ๋ฅผ ๋‹ค์‹œ ์‹คํ–‰ํ•˜๋Š” ๊ฒƒ์„ Skip ํ•œ๋‹ค.

import { useState, useRef, useEffect } from 'react';

function VideoPlayer({ src, isPlaying }) {
  const ref = useRef(null);

  useEffect(() => {
    if (isPlaying) {
      console.log('Calling video.play()');
      ref.current.play();
    } else {
      console.log('Calling video.pause()');
      ref.current.pause();
    }
  }, []); // >> This causes an error

  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 ? 'Pause' : 'Play'}
      </button>
      <VideoPlayer
        isPlaying={isPlaying}
        src='https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4'
      />
    </>
  );
}

์•Œ์•„๋‘˜ ๊ฒƒ

React๋Š” Object.is ๋น„๊ต๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ข…์†์„ฑ์˜ ๊ฐ’์„ ๋น„๊ตํ•œ๋‹ค. ์ง€์ •ํ•œ ์ข…์†์„ฑ์ด Effect ๋‚ด๋ถ€์˜ ์ฝ”๋“œ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ React๊ฐ€ ์˜ˆ์ƒํ•˜๋Š” ๊ฒƒ๊ณผ ์ผ์น˜ํ•˜์ง€ ์•Š์œผ๋ฉด ๋ฆฐํŠธ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค.

์—ฌ๊ธฐ๊นŒ์ง€ ์ •๋ฆฌ

useEffect(() => {
  // This runs after every render
});

useEffect(() => {
  // This runs only on mount (when the component appears)
}, []);

useEffect(() => {
  // This runs on mount _and also_ if either a or b have changed since the last render
}, [a, b]);

ํด๋ฆฝ์—… ์ฝ”๋“œ

์•„๋ž˜์˜ ์ฝ”๋“œ๋Š” 3rd Party ์ฑ„ํŒ… ์„œ๋ฒ„๋ฅผ ์—ฐ๊ฒฐํ•˜๋Š” ์ฝ”๋“œ์ด๋‹ค.

useEffect(() => {
  const connection = createConnection();
  connection.connect();
});

์ดํŽ™ํŠธ๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋งˆ์šดํŠธ ๋  ๋–„(๋ฆฌ๋ Œ๋”๋ง ๋  ๋•Œ) ๊ณ„์†ํ•ด์„œ ์‹คํ–‰๋˜๊ธฐ ๋•Œ๋ฌธ์—, ๋นˆ๋ฐฐ์—ด([])์„ ์ข…์†์„ฑ์— ์ถ”๊ฐ€ํ•˜์ž.

useEffect(() => {
  const connection = createConnection();
  connection.connect();
}, []);

ํ•˜์ง€๋งŒ, ์ด๊ฒƒ๋„ ์ข‹์€ ๋ฐฉ๋ฒ•์ด๋ผ๊ณ  ํ•  ์ˆ˜ ์—†๋‹ค.

์™œ? ๐Ÿง

ํ•ด๋‹น ์ปดํฌ๋„ŒํŠธ๊ฐ€ ํฐ ์•ฑ์˜ ์ผ๋ถ€๋ผ๊ณ  ๊ฐ€์ •ํ•˜๊ณ , ํ•ด๋‹น ์ปดํฌ๋„ŒํŠธ๊ฐ€ ํ™”๋ฉด์— ํ‘œ์‹œ๋˜์–ด ์ดํŽ™ํŠธ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค. ๊ทธ ํ›„, ๋’ค๋กœ ๊ฐ€๊ธฐ ๋ฒ„ํŠผ์„ ํด๋ฆญํ•˜๊ณ , ๋‹ค์‹œ ์ปดํฌ๋„ŒํŠธ๋กœ ๋“ค์–ด์˜จ๋‹ค. ์ด ์‚ฌ์šฉ์ž ๋™์ž‘์—์„œ์˜ ์ด์Šˆ๋Š”, ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋งˆ์šดํŠธ ๋˜๊ณ  ํ•ด์ œ ๋˜์—ˆ์„ ๋•Œ, ์ฑ„ํŒ… ์„œ๋ฒ„ ์—ฐ๊ฒฐ์ด ๊ณ„์† ๋‚จ์•„์žˆ์–ด ์Œ“์ด๊ฒŒ ๋œ๋‹ค๋Š” ๊ฒƒ์— ์žˆ๋‹ค.

์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ํด๋ฆฐ์—… ์ฝ”๋“œ๋ฅผ ์ถ”๊ฐ€ํ•ด์•ผ ํ•œ๋‹ค.

import { useState, useEffect } from 'react';
import { createConnection } from './chat.js';

export default function ChatRoom() {
  useEffect(() => {
    const connection = createConnection();
    connection.connect();

    return () => connection.disconnect(); // clean up
  }, []);
  return <h1>Welcome to the chat!</h1>;
}

React๋Š” Effect๊ฐ€ ๋‹ค์‹œ ์‹คํ–‰๋˜๊ธฐ ์ „์— ๋งค๋ฒˆ ํด๋ฆฐ์—… ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•˜๊ณ , ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋งˆ์šดํŠธ ํ•ด์ œ(์ œ๊ฑฐ)๋  ๋•Œ ๋งˆ์ง€๋ง‰์œผ๋กœ ํ•œ ๋ฒˆ ํ˜ธ์ถœํ•œ๋‹ค. ์—ฐ๊ฒฐ์„ ๋Š์—ˆ๋‹ค๊ฐ€ ๋‹ค์‹œ ์—ฐ๊ฒฐํ•˜๋Š” ๊ฒƒ์ด ์˜ฌ๋ฐ”๋ฅธ ๋™์ž‘์ด๋‹ค.

๊ฐœ๋ฐœ ์ค‘์— ์ดํŽ™ํŠธ๊ฐ€ ๋‘ ๋ฒˆ ์‹คํ–‰๋˜๋Š” ๊ฒƒ์„ ์–ด๋–ป๊ฒŒ ์ฒ˜๋ฆฌํ•˜๋Š”๊ฐ€

<React.StrictMode> ๋กœ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๊ฐ์‹ผ๋‹ค๋ฉด, Effect ๋“ฑ์„ ๋‘ ๋ฒˆ์”ฉ ์‹คํ–‰ํ•œ๋‹ค. ์ด๊ฒƒ์€ React๊ฐ€ ๋ฒ„๊ทธ๋ฅผ ์ฐพ๊ธฐ ์œ„ํ•ด์„œ ๊ฐœ๋ฐœ์ค‘์—๋Š” ์˜๋„์ ์œผ๋กœ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋‹ค์‹œ ๋งˆ์šดํŠธํ•˜๋Š” ๊ฒƒ์—์„œ ๊ธฐ์ธํ•œ ๋™์ž‘์ด๋‹ค. ๋‘๋ฒˆ ํ˜ธ์ถœ๋˜์ง€ ์•Š๋„๋ก ํ•˜๊ธฐ ์œ„ํ•œ ๋ฐฉ๋ฒ•์€ ์ดํŽ™ํŠธ๋ฅผ ํ•œ ๋ฒˆ ์‹คํ–‰ํ•˜๋Š” ๋ฐฉ๋ฒ•์ด ์•„๋‹ˆ๋ผ ์ดํŽ™ํŠธ๋ฅผ ๋‹ค์‹œ ๋งˆ์šดํŠธ ํ•œ ํ›„์—๋„ ์ž‘๋™ํ•˜๋„๋ก ์ˆ˜์ • ํ•˜๋Š” ๋ฐฉ๋ฒ•์ด๋‹ค. ์ฆ‰, ํด๋ฆฐ์—… ํ•จ์ˆ˜๋ฅผ ๊ตฌํ˜„ํ•˜๋Š” ๊ฒƒ์ด๋‹ค.

ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ์—์„œ๋Š” ๋‘๋ฒˆ ํ˜ธ์ถœ๋˜์ง€ ์•Š๋Š”๋‹ค.

๋‚ด์žฅ๋œ dialog ์š”์†Œ์˜ showModal ๋ฉ”์„œ๋“œ๋Š” ๋‘ ๋ฒˆ ํ˜ธ์ถœํ•˜๋ฉด ์—๋Ÿฌ ๋ฐœ์ƒ

  • ํด๋ฆฐ์—… ํ•จ์ˆ˜ ๊ตฌํ˜„

useEffect(() => {
  const dialog = dialogRef.current;
  dialog.showModal();
  return () => dialog.close();
}, []);

๊ตฌ๋…ํ•˜๋Š” ํ•ญ๋ชฉ์ด ์žˆ๋Š” ๊ฒฝ์šฐ ํด๋ฆฐ์—… ํ•จ์ˆ˜์—์„œ ๊ตฌ๋… ์ทจ์†Œ

useEffect(() => {
  function handleScroll(e) {
    console.log(window.scrollX, window.scrollY);
  }
  window.addEventListener('scroll', handleScroll);
  return () => window.removeEventListener('scroll', handleScroll);
}, []);

์• ๋‹ˆ๋ฉ”์ด์…˜์ด ์ ์šฉ๋œ ๊ฒฝ์šฐ ํด๋ฆฐ์—… ํ•จ์ˆ˜๋Š” ์• ๋‹ˆ๋ฉ”์ด์…˜ ๊ฐ’์„ ์ดˆ๊ธฐ ๊ฐ’์œผ๋กœ ์žฌ์„ค์ •

useEffect(() => {
  const node = ref.current;
  node.style.opacity = 1; // Trigger the animation
  return () => {
    node.style.opacity = 0; // Reset to the initial value
  };
}, []);

๋ฌด์–ธ๊ฐ€๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ๊ฒฝ์šฐ ์ •๋ฆฌ ํ•จ์ˆ˜๋Š” ๊ฐ€์ ธ์˜ค๊ธฐ๋ฅผ ์ค‘๋‹จํ•˜๊ฑฐ๋‚˜ ๊ฒฐ๊ณผ๋ฅผ ๋ฌด์‹œ

useEffect(() => {
  let ignore = false;

  async function startFetching() {
    const json = await fetchTodos(userId);
    if (!ignore) {
      setTodos(json);
    }
  }

  startFetching();

  return () => {
    ignore = true;
  };
}, [userId]);

์ด๋ฏธ ๋ฐœ์ƒํ•œ ๋„คํŠธ์›Œํฌ ์š”์ฒญ์„ ์‹คํ–‰ ์ทจ์†Œ ํ•  ์ˆ˜ ์—†๊ธฐ ๋•Œ๋ฌธ์—, ๋˜ํ•œ ๋„คํŠธ์›Œํฌ ์š”์ฒญ์ด ๋Šฆ์–ด์งˆ ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์—, ํด๋ฆฐ์—… ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋” ์ด์ƒ ๊ด€๋ จ ์—†๋Š” ์‹คํ–‰์ด ์˜ํ–ฅ์„ ๋ฏธ์น˜์ง€ ์•Š๋„๋ก ์ œ์–ดํ•ด์ค€๋‹ค.

์ฑŒ๋ฆฐ์ง€

์ด ์นด์šดํ„ฐ ์ปดํฌ๋„ŒํŠธ๋Š” ๋งค์ดˆ๋งˆ๋‹ค ์ฆ๊ฐ€ํ•ด์•ผ ํ•˜๋Š” ์นด์šดํ„ฐ๋ฅผ ํ‘œ์‹œํ•œ๋‹ค. ๋งˆ์šดํŠธ ์‹œ์—๋Š” setInterval์„ ํ˜ธ์ถœํ•˜๋Š”๋ฐ ๊ทธ๋Ÿฌ๋ฉด onTick์ด ๋งค์ดˆ๋งˆ๋‹ค ์‹คํ–‰๋˜๊ณ , 2์”ฉ ์ฆ๊ฐ€์‹œํ‚จ๋‹ค.

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>;
}
  • ์›์ธ

    • strict mode ์ธ ๊ฒฝ์šฐ React๋Š” ๊ฐ ์ปดํฌ๋„ŒํŠธ๋ฅผ ํ•œ ๋ฒˆ์”ฉ ๋‹ค์‹œ ๋งˆ์šดํŠธํ•œ๋‹ค. ์ด๋กœ ์ธํ•ด ์นด์šดํŠธ๊ฐ€ 2์”ฉ ์ฆ๊ฐ€๋œ๋‹ค. ์›์ธ์€ ์ปดํฌ๋„ŒํŠธ๊ฐ€ 2๋ฒˆ ๋งˆ์šดํŠธ ๋˜๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ, ์ดํŽ™ํŠธ ์‹คํ–‰์—์„œ ํด๋ฆฐ์—… ํ•จ์ˆ˜๋ฅผ ์ œ๊ณตํ•˜๊ณ  ์žˆ์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

  • ํ•ด๊ฒฐ๋ฐฉ๋ฒ•

    • 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, 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>
    </>
  );
}
  • ์›์ธ

    • ๋ฒ„๊ทธ์˜ ์›์ธ์€ ๋‘๊ฐœ์˜ ๋น„๋™๊ธฐ ์—ฐ์‚ฐ์ด ์„œ๋กœ ๊ฒฝ์Ÿ(race condition)ํ•˜๊ณ  ์žˆ๋‹ค๋Š” ๊ฒƒ์— ์žˆ๋‹ค.

    • strict mode ์ธ ๊ฒฝ์šฐ React๋Š” ๊ฐ ์ปดํฌ๋„ŒํŠธ๋ฅผ ํ•œ ๋ฒˆ์”ฉ ๋‹ค์‹œ ๋งˆ์šดํŠธํ•œ๋‹ค. ๋กœ ์ธํ•ด ์นด์šดํŠธ๊ฐ€ 2์”ฉ ์ฆ๊ฐ€๋œ๋‹ค. ์›์ธ์€ ์ปดํฌ๋„ŒํŠธ๊ฐ€ 2๋ฒˆ ๋งˆ์šดํŠธ ๋˜๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ, ์ดํŽ™ํŠธ ์‹คํ–‰์—์„œ ํด๋ฆฐ์—… ํ•จ์ˆ˜๋ฅผ ์ œ๊ณตํ•˜๊ณ  ์žˆ์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

  • ํ•ด๊ฒฐ๋ฐฉ๋ฒ•

    • race condition ๋ฒ„๊ทธ๋ฅผ ์ˆ˜์ •ํ•˜๊ธฐ ์œ„ํ•ด์„œ ํด๋ฆฐ์—… ํ•จ์ˆ˜๋ฅผ ์ถ”๊ฐ€ํ•œ๋‹ค.

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>
    </>
  );
}

๊ฐ๊ฐ์˜ ๋ Œ๋”๋ง์˜ useEffect ์—๋Š” ๊ณ ์œ ํ•œ ๋ณ€์ˆ˜ ignore ๊ฐ€ ์กด์žฌํ•œ๋‹ค. ์ฒ˜์Œ ๊ธฐ๋ณธ ๊ฐ’์€ false์ด๋‹ค. ํ•˜์ง€๋งŒ, ๋‹ค๋ฅธ ์‚ฌ๋žŒ์„ ์„ ํƒํ•˜๋Š” ๊ฒฝ์šฐ ignore ๊ฐ’์€ true ๊ฐ€ ๋œ๋‹ค.

์ด์ œ ์š”์ฒญ์ด ์™„๋ฃŒ๋˜๋Š” ์ˆœ์„œ๋Š” ์ค‘์š”ํ•˜์ง€ ์•Š๊ณ , ๋งˆ์ง€๋ง‰์— ์„ ํƒํ•œ ์‚ฌ๋žŒ์˜ ignore ๊ฐ’๋งŒ false ๊ฐ€ ๋˜๊ณ , ํ•ด๋‹น ์‚ฌ๋žŒ์˜ ์ •๋ณด๋งŒ ํ‘œ์‹œํ•œ๋‹ค.

  • 'Bob'์„ ์„ ํƒํ•˜๋ฉด fetchBio('Bob')๊ฐ€ ํŠธ๋ฆฌ๊ฑฐ

  • 'Taylor'๋ฅผ ์„ ํƒํ•˜๋ฉด fetchBio('Taylor')๊ฐ€ ํŠธ๋ฆฌ๊ฑฐ๋˜๊ณ  ์ด์ „ (Bob์˜) ์ดํŽ™ํŠธ๊ฐ€ ์ •๋ฆฌ

  • 'Bob'์„ ๊ฐ€์ ธ์˜ค๊ธฐ ์ „์— 'Taylor' ๊ฐ€์ ธ์˜ค๊ธฐ๊ฐ€ ์™„๋ฃŒ

  • 'Taylor' ๋ Œ๋”์˜ ์ดํŽ™ํŠธ๋Š” setBio('์ด๊ฒƒ์€ Taylor์˜ ๋ฐ”์ด์˜ค์ž…๋‹ˆ๋‹ค')๋ฅผ ํ˜ธ์ถœ

  • 'Bob' ๋ถˆ๋Ÿฌ์˜ค๊ธฐ ์™„๋ฃŒ

'Bob' ๋ Œ๋”๋ง์˜ ์ดํŽ™ํŠธ๋Š” ignore ํ”Œ๋ž˜๊ทธ๊ฐ€ true๋กœ ์„ค์ •๋˜์—ˆ๊ธฐ ๋•Œ๋ฌธ์— ์•„๋ฌด ์ž‘์—…๋„ ์ˆ˜ํ–‰ํ•˜์ง€ ์•Š๋Š”๋‹ค. ์˜ค๋ž˜๋œ API ํ˜ธ์ถœ์˜ ๊ฒฐ๊ณผ๋ฅผ ๋ฌด์‹œํ•˜๋Š” ๊ฒƒ ์™ธ์—๋„ AbortController๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋” ์ด์ƒ ํ•„์š”ํ•˜์ง€ ์•Š์€ ์š”์ฒญ์„ ์ทจ์†Œํ•  ์ˆ˜๋„ ์žˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ์ด๊ฒƒ๋งŒ์œผ๋กœ๋Š” race condition์„ ๋ฐฉ์ง€ํ•˜๊ธฐ์— ์ถฉ๋ถ„ํ•˜์ง€ ์•Š๋‹ค.

AbortController๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ๋” ๋งŽ์€ ๋น„๋™๊ธฐ ๋‹จ๊ณ„๊ฐ€ ์—ฐ์‡„์ ์ผ ๊ฐ€๋Šฅ์„ฑ์ด ์žˆ๊ธฐ ๋•Œ๋ฌธ์—, ignore ์™€ ๊ฐ™์€ ๋ช…์‹œ์  ํ”Œ๋ž˜๊ทธ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ๋” ์•ˆ์ •์ ์ธ ๋ฐฉ๋ฒ•์ด๋‹ค.

React๊ฐ€ effect๋ฅผ ์ •๋ฆฌ(clean-up)ํ•˜๋Š” ์‹œ์ ์€ ์ •ํ™•ํžˆ ์–ธ์ œ์ธ๊ฐ€

์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋งˆ์šดํŠธ ํ•ด์ œ๋˜๋Š” ์‹œ์ ์— ์ •๋ฆฌ ํ•จ์ˆ˜๋ฅผ ์‹คํ–‰ํ•œ๋‹ค. ์ดํŽ™ํŠธ๋Š” ๋ Œ๋”๋ง ๋  ๋–„ ๋งˆ๋‹ค ์‹คํ–‰๋œ๋‹ค. ๋”ฐ๋ผ์„œ ๋‹ค์Œ ์ฐจ๋ก€์˜ ์ดํŽ™ํŠธ๋ฅผ ์‹คํ–‰ํ•˜๊ธฐ ์ „์— ์ด์ „์˜ ๋ Œ๋”๋ง์—์„œ ํŒŒ์ƒ๋œ ์ดํŽ™ํŠธ๋ฅผ ์ •๋ฆฌํ•œ๋‹ค. ์ด๊ฒƒ์€ ๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜๋„ ๋ฐฉ์ง€ํ•˜๋ฉฐ, ๋ฒ„๊ทธ๋ฅผ ๋ฐฉ์ง€ํ•˜๋Š”๋ฐ ๋„์›€์ด ๋œ๋‹ค.

์ •๋ฆฌ

  • ์ด๋ฒคํŠธ์™€ ๋‹ฌ๋ฆฌ useEffect๋Š” ํŠน์ • ์ƒํ˜ธ์ž‘์šฉ์ด ์•„๋‹Œ ๋ Œ๋”๋ง ์ž์ฒด๋กœ ์ธํ•ด ๋ฐœ์ƒ

  • useEffect๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์ปดํฌ๋„ŒํŠธ๋ฅผ ์™ธ๋ถ€ ์‹œ์Šคํ…œ(ํƒ€์‚ฌ API, ๋„คํŠธ์›Œํฌ)๊ณผ ๋™๊ธฐํ™” ํ•  ์ˆ˜ ์žˆ์Œ

  • ๊ธฐ๋ณธ์ ์œผ๋กœ useEffect๋Š” ๋ชจ๋“  ๋ Œ๋”๋ง(์ดˆ๊ธฐ ๋ Œ๋”๋ง ํฌํ•จ) ํ›„์— ์‹คํ–‰

  • ๋ชจ๋“  ์ข…์†์„ฑ์ด ๋งˆ์ง€๋ง‰ ๋ Œ๋”๋ง ๋•Œ์™€ ๋™์ผํ•œ ๊ฐ’์„ ๊ฐ€์ง„๋‹ค๋ฉด useEffect๊ฐ€ ์‹คํ–‰๋˜์ง€ ์•Š์Œ

  • ๋นˆ ์˜์กด์„ฑ ๋ฐฐ์—ด([])์€ ์ปดํฌ๋„ŒํŠธ ๋งˆ์šดํŒ…, ์ฆ‰ ํ™”๋ฉด์— ์ถ”๊ฐ€๋˜๋Š” ๊ฒƒ์— ํ•ด๋‹น๋จ

  • Strict ๋ชจ๋“œ์—์„œ React๋Š” ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋‘ ๋ฒˆ ๋งˆ์šดํŠธํ•˜์—ฌ(๊ฐœ๋ฐœ ์ค‘์ผ ๋•Œ๋งŒ!) Effect๋ฅผ ์ŠคํŠธ๋ ˆ์Šค ํ…Œ์ŠคํŠธ

  • ๋‹ค์‹œ ๋งˆ์šดํŠธํ•˜๋Š” ๊ณผ์ •์—์„œ ์ดํŽ™ํŠธ๊ฐ€ ์ค‘๋‹จ๋˜๋ฉด ์ •๋ฆฌ ํ•จ์ˆ˜๋ฅผ ๊ตฌํ˜„ํ•ด์•ผ ํ•œ๋‹ค. (CleanUp)

  • React๋Š” ๋‹ค์Œ ๋ฒˆ์— ์ดํŽ™ํŠธ๊ฐ€ ์‹คํ–‰๋˜๊ธฐ ์ „๊ณผ ๋งˆ์šดํŠธ ํ•ด์ œ ์ค‘์— ์ •๋ฆฌ ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•œ๋‹ค.

์ฝ์–ด๋ณด๊ธฐ

A Complete Guide to useEffect

Last updated