UseEffect

  • ๋ถ€์ˆ˜์  ํšจ๊ณผ๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•˜๋Š”๋ฐ ๋ Œ๋”๋ง ์ฝ”๋“œ, ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ๋งŒ์œผ๋กœ ์ถฉ๋ถ„ํ•˜์ง€ ์•Š์„ ๋•Œ

  • ํ™”๋ฉด์ด ์—…๋ฐ์ดํŠธ๋˜๊ณ  ์ปค๋ฐ‹์ด ๋๋‚˜๋ฉด ํšจ๊ณผ๊ฐ€ ์‹คํ–‰

UseEffect ์‚ฌ์šฉ๋ฒ•

์ฐธ์กฐ

setup: ํšจ๊ณผ๋ฅผ ์ˆ˜ํ–‰ํ•˜๊ธฐ ์œ„ํ•œ ํ•จ์ˆ˜

dependencies: setup ๋‚ด๋ถ€์—์„œ ์ฐธ์กฐํ•˜๊ณ  ์žˆ๋Š” react์˜ ๊ฐ’๋“ค ๋ฐฐ์—ด. ๋ฆฌ๋ Œ๋”๋ง ์‹œ๋งˆ๋‹ค Object.is๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ด์ „ ์ข…์†์„ฑ๊ณผ ํ˜„์žฌ ์ข…์†์„ฑ์„ ๋น„๊ตํ•˜์—ฌ ๊ฐ™์œผ๋ฉด ํšจ๊ณผ๋ฅผ ๊ฑด๋„ˆ๋›ด๋‹ค.

useEffect(setup, dependencies?)

์‚ฌ์šฉ๋ฒ•

  1. useEffect ์„ ์–ธํ•˜๊ธฐ

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

  2. dependencies array ์ง€์ •ํ•˜๊ธฐ

    • ํ•„์š”ํ•œ ๋•Œ์—๋งŒ ์‹คํ–‰๋˜๋„๋ก ์ข…์†์„ฑ์„ ์ง€์ •ํ•œ๋‹ค.

  3. cleanup function ์ถ”๊ฐ€ํ•˜๊ธฐ

    • ์ปดํฌ๋„ŒํŠธ๊ฐ€ DOM์—์„œ ์ œ๊ฑฐ๋  ๋•Œ ์ฒ˜๋ฆฌํ•  ํ•จ์ˆ˜๋ฅผ ์ง€์ •ํ•œ๋‹ค.

์™ธ๋ถ€์™€ ์—ฐ๊ฒฐ ๋ฐ ์—ฐ๊ฒฐํ•ด์ œ

  • socket ์—ฐ๊ฒฐ๊ณผ ๊ฐ™์€ ์™ธ๋ถ€ ์—ฐ๊ฒฐ ํ›„ ์—ฐ๊ฒฐ ํ•ด์ œ

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

์ด๋ฒคํŠธ ๋“ฑ๋ก ๋ฐ ์ œ๊ฑฐ

  • eventListener๋ฅผ ๋“ฑ๋กํ•˜๊ณ  ํ•ด์ œ

  • ์•ˆ์“ฐ๋Š” ์ด๋ฒคํŠธ๋Š” ํ•ญ์ƒ ๊ทธ๋•Œ ๊ทธ๋•Œ ํ•ด์ œํ•ด์•ผ ํ•œ๋‹ค.

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

2์”ฉ ์ฆ๊ฐ€ํ•˜๋Š” ์นด์šดํ„ฐ

  • React์—์„œ Strict๋ชจ๋“œ๊ฐ€ ํ™œ์„ฑํ™”๋˜๋ฉด ๋‘ ๋ฒˆ์”ฉ ๋ Œ๋”๋ง๋œ๋‹ค.

  • ์ ์ ˆํ•œ ์ •๋ฆฌ ํ•จ์ˆ˜๋ฅผ ๋งŒ๋“ค์–ด ๋†“์ง€ ์•Š์œผ๋ฉด setInterval์ด ๋‘ ๋ฒˆ ์‹คํ–‰๋˜์–ด 2์”ฉ ์ฆ๊ฐ€ํ•˜๊ฒŒ ๋œ๋‹ค.

  • Strict ๋ชจ๋“œ์˜ ๋ฌธ์ œ๊ฐ€ ์•„๋‹ˆ๋‹ค. ์ปดํฌ๋„ŒํŠธ๋Š” ์—ฌ๋Ÿฌ ๋ฒˆ ๋ Œ๋”๋ง๋˜๋”๋ผ๋„ ๋ณต์›๋ ฅ์„ ๊ฐ€์ง€๊ณ  ์žˆ์–ด์•ผ ํ•œ๋‹ค.

export default function Counter() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    function onTick() {
      setCount(c => c + 1);
    }
    setInterval(onTick, 1000);
  }, []);

  return <h1>{count}</h1>;
}
...  
useEffect(() => {
    function onTick() {
      setCount(c => c + 1);
    }
    const id = setInterval(onTick, 1000);
    return () => {
        clearInterval(id)
    }
  }, []);
...

๋ฐ์ดํ„ฐ fetchํ•˜๊ธฐ

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

  async function startFetching() {
    const json = await fetchTodos(userId);
    if(isFetched) return
      
    //json์„ ๋‹ค๋ฃจ๋Š” ๋กœ์ง
      setTodos(json)
  }

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

์•ฑ ๋กœ๋“œ๋‹น ํ•œ ๋ฒˆ ์‹คํ–‰ํ•˜๊ธฐ

  • ์ผ๋ฐ˜์ ์œผ๋กœ ์ฝ”๋“œ์˜ ์ตœ์ƒ์œ„(์ปดํฌ๋„ŒํŠธ ๋ฐ–)์—๋Š” ๋ณ€์ˆ˜(let,var)๋ฅผ ์„ ์–ธํ•˜์ง€ ์•Š๋Š”๋‹ค.

  • ํ•˜์ง€๋งŒ ์•ฑ์„ ์ตœ์ดˆ ๋กœ๋“œํ•  ๋•Œ ํ•œ ๋ฒˆ๋งŒ ์‹คํ–‰ํ•ด์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ

  • ์ตœ์ƒ์œ„ ๋ณ€์ˆ˜๋ฅผ ์„ ์–ธํ•˜์—ฌ useEffect๊ฐ€ ์‹คํ–‰๋˜์—ˆ๋Š”์ง€ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค

let didInit = false;

function App() {
  useEffect(() => {
    if (!didInit) {
      didInit = true;
      // โœ… Only runs once per app load
      loadDataFromLocalStorage();
      checkAuthToken();
    }
  }, []);
  // ...
}
  • ๋…๋ฆฝ์ ์œผ๋กœ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ๋Š” ํ•จ์ˆ˜๋ผ๋ฉด ์ปดํฌ๋„ŒํŠธ ๋ฐ–์—์„œ ํ˜ธ์ถœํ•  ์ˆ˜๋„ ์žˆ๋‹ค.

  • ํ•˜์ง€๋งŒ ํ•ด๋‹น ํ•จ์ˆ˜๊ฐ€ ์˜ค๋ž˜ ๊ฑธ๋ฆฐ๋‹ค๋ฉด App ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋กœ๋“œํ•˜๋Š” ๋ฐ ์ง€์—ฐ์ด ๋ฐœ์ƒํ•˜๊ฒŒ ๋˜๋ฏ€๋กœ ๋‚จ์šฉํ•ด์„œ๋Š” ์•ˆ๋œ๋‹ค

if (typeof window !== 'undefined') { // Check if we're running in the browser.
   // โœ… Only runs once per app load
  checkAuthToken();
  loadDataFromLocalStorage();
}

function App() {
  // ...
}

๋ฌดํ•œ๋ฃจํ”„

  • ๋ Œ๋”๋ง ํ›„ useEffect ์•ˆ์—์„œ state ๋ณ€๊ฒฝ -> ๋ฆฌ๋ Œ๋”๋ง ->๋ Œ๋”๋ง ํ›„ useEffect ์•ˆ์—์„œ state ๋ณ€๊ฒฝ ->... ๋ฌดํ•œ ๋ฐ˜๋ณต

const [count, setCount] = useState(0);
useEffect(() => {
  setCount(count + 1);
});

๋ถˆํ•„์š”ํ•œ ํšจ๊ณผ ์ œ๊ฑฐ

  • ์™ธ๋ถ€ ์‹œ์Šคํ…œ๊ณผ์˜ ๋™๊ธฐํ™”๊ฐ€ ํ•„์š”ํ•˜์ง€ ์•Š์œผ๋ฉด ํšจ๊ณผ๊ฐ€ ํ•„์š”ํ•œ ๊ฒƒ์ด ์•„๋‹ ์ˆ˜ ์žˆ๋‹ค.

๋ Œ๋”๋ง์— ํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ ๋งŒ๋“ค๊ธฐ

  • ๋ Œ๋”๋ง์„ ์œ„ํ•ด ๋ฐ์ดํ„ฐ๋ฅผ ๋ณ€ํ™˜ํ•˜๋Š” ๋ฐ ํšจ๊ณผ๋ฅผ ์‚ฌ์šฉํ•  ํ•„์š”๊ฐ€ ์—†๋‹ค. ex. ์ปดํฌ๋„ŒํŠธ์˜ ์ตœ์ƒ์œ„ ์ˆ˜์ค€์—์„œ ๋ณ€ํ™˜ํ•˜๊ณ  useMemo๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฉ”๋ชจ์ œ์ด์…˜ํ•˜๋ฉด ๋œ๋‹ค

const [todos, setTodos] = useState([]);
const doneTodos = useMemo(()=> todos.filter(todo => todo.isDone), [todos])

prop ๋ณ€๊ฒฝ ์‹œ ์ƒํƒœ๋ฅผ ์žฌ์„ค์ •ํ•˜๊ธฐ

  • prop ๊ฐ’์ธ userId์ด ๋ณ€๊ฒฝ๋  ๋•Œ ๋งˆ๋‹ค comment๋ฅผ ์ดˆ๊ธฐํ™”๋ฅผ ํ•ด์•ผ ํ•œ๋‹ค

  • useEffect๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด, ์ผ๋‹จ์€ ์˜ค๋ž˜๋œ ๊ฐ’์œผ๋กœ ๋ Œ๋”๋ง ๋œ ํ›„์—

  • useEffect๋ฅผ ํ†ตํ•ด ๋‹ค์‹œ ์ƒˆ๋กœ์šด ๊ฐ’์œผ๋กœ ๋ณ€๊ฒฝ๋˜๊ณ  ๋ฆฌ๋ Œ๋”๋ง๋œ๋‹ค

//โŒ userId ๋ณ€๊ฒฝ ์‹œ comment๋ฅผ ์ดˆ๊ธฐํ™”
export default function ProfilePage({ userId }) {
  const [comment, setComment] = useState('');

  useEffect(() => {
    setComment('');
  }, [userId]);
  // ...
}

์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ์— ๋„ฃ๊ธฐ

function Toggle({ onChange }) {
  const [isOn, setIsOn] = useState(false);

  // ๐Ÿ”ด useEffect๋ฅผ ์‚ฌ์šฉํ•  ํ•„์š”๊ฐ€ ์—†๋‹ค
  useEffect(() => {
    onChange(isOn);
  }, [isOn, onChange])

  function handleClick() {
    setIsOn(!isOn);
  }

  // ...
}

์ข…์†์„ฑ ๋ฐฐ์—ด

  • ์ข…์†์„ฑ ๋ฐฐ์—ด์€ useEffect์˜ Effect ๋‚ด์—์„œ ์‚ฌ์šฉํ•˜๋Š”(์˜์กดํ•˜๋Š”) react ๊ฐ’(props์™€ state ๊ทธ๋ฆฌ๊ณ  ์ด๋“ค๋กœ ๊ณ„์‚ฐ๋œ ๊ฐ’๋“ค)์˜ ๋ชฉ๋ก์ด๋‹ค

  • ์‹œ๊ฐ„์— ๋”ฐ๋ผ ๋ณ€ํ•  ์ˆ˜ ์žˆ๋Š” ๊ฐ’๋“ค์ด๊ธฐ ๋•Œ๋ฌธ์— ๋ฐฐ์—ด๋กœ ๋ณ€ํ™”๋ฅผ ๊ฐ์ง€ํ•œ๋‹ค

  • ๋”ฐ๋ผ์„œ ์ „์—ญ ๋ณ€์ˆ˜, ์ƒ์ˆ˜์™€ ๊ฐ™์€ react ๊ฐ’์ด ์•„๋‹Œ ๋ฐ์ดํ„ฐ๋“ค์€ ์ข…์†์„ฑ ๋ฐฐ์—ด์— ๋„ฃ์ง€ ์•Š์•„๋„ ๋œ๋‹ค.

  • ๋นˆ ์ข…์†์„ฑ ๋ฐฐ์—ด์€ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋งˆ์šดํŠธ ๋  ๋•Œ Effect๊ฐ€ ์‹คํ–‰๋˜๊ณ , ๋งˆ์šดํŠธ ํ•ด์ œ๋  ๋•Œ ์ •๋ฆฌ ํ•จ์ˆ˜๋ฅผ ์‹คํ–‰ํ•œ๋‹ค

๋ฉˆ์ถ”๋Š” ์นด์šดํ„ฐ

  • ํ•ด๋‹น ์นด์šดํ„ฐ setInterval()์„ ์‚ฌ์šฉํ•˜์—ฌ 1์ดˆ๋งˆ๋‹ค increment ๊ฐ’์„ count์— ๋”ํ•˜๋Š” ํƒ€์ด๋จธ ์ปดํฌ๋„ŒํŠธ์ด๋‹ค

  • ์ผ๋ฐ˜์ ์ธ ๊ฒฝ์šฐ์—๋Š” ์ž˜ ๋™์ž‘ํ•˜๋Š” ๊ฒƒ์ฒ˜๋Ÿผ ๋ณด์ด์ง€๋งŒ, ์ฆ๊ฐ€ ๋˜๋Š” ๊ฐ์†Œ ๋ฒ„ํŠผ์„ 1์ดˆ์— ์—ฌ๋Ÿฌ ๋ฒˆ ํด๋ฆญํ•˜๋ฉด ํƒ€์ด๋จธ๊ฐ€ ๋ฉˆ์ถ˜๊ฒƒ์ฒ˜๋Ÿผ ๋ณด์ธ๋‹ค.

  • ๊ทธ ์ด์œ ๋Š” useEffect์—์„œ increment ๊ฐ’์ด ๋ณ€๊ฒฝ๋  ๋•Œ๋งˆ๋‹ค setInterval์ด ๋‹ค์‹œ ์‹œ์ž‘๋˜๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

//โŒ 1์ดˆ์— ์—ฌ๋Ÿฌ ๋ฒˆ ์ฆ๊ฐ€ ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅด๋ฉด ํƒ€์ด๋จธ๊ฐ€ ๋ฉˆ์ถ˜ ๊ฒƒ ์ฒ˜๋Ÿผ ๋ณด์ž…๋‹ˆ๋‹ค
import { useState, useEffect } from 'react';
import { experimental_useEffectEvent as useEffectEvent } from 'react';

export default function Timer() {
  const [count, setCount] = useState(0);
  const [increment, setIncrement] = useState(1);

  useEffect(() => {
    const id = setInterval(() => {
      setCount(c => c + increment);
    }, 1000);
    return () => {
      clearInterval(id);
    };
  }, [increment]);

  return (
    <>
      <h1>
        Counter: {count}
        <button onClick={() => setCount(0)}>Reset</button>
      </h1>
      <hr />
      <p>
        Every second, increment by:
        <button disabled={increment === 0} onClick={() => {
          setIncrement(i => i - 1);
        }}>โ€“</button>
        <b>{increment}</b>
        <button onClick={() => {
          setIncrement(i => i + 1);
        }}>+</button>
      </p>
    </>
  );
}
  • increment๊ฐ’์„ useRef๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ setCount์— ๋„˜๊ฒจ์ฃผ๋ฉด useEffect์˜ ์˜์กด์„ฑ ๋ฐฐ์—ด์— increment๋ฅผ ๋„ฃ์ง€ ์•Š์•„๋„ ๋œ๋‹ค.

  • ๋”ฐ๋ผ์„œ increment ๊ฐ’์ด ๋ณ€๊ฒฝ๋˜๋”๋ผ๋„ setInterval ํ•จ์ˆ˜๊ฐ€ ์žฌ์‹œ์ž‘๋˜์ง€ ์•Š๋Š”๋‹ค.

...
const [count, setCount] = useState(0);
const incrementRef = useRef(increment);

  useEffect(() => {
    incrementRef.current = increment;
  }, [increment]);

  useEffect(() => {
  
    const id = setInterval(() => {
      setCount(c => c + incrementRef.current);
    }, 1000);

    return () => {
      clearInterval(id);
    };
    
  }, []);
...

์ข…์†์„ฑ์€ ์„ ํƒ์ด ์•„๋‹ˆ๋‹ค

  • ์ข…์†์„ฑ ๋ฐฐ์—ด์— ์ข…์†์„ฑ์„ ์ง€์ •ํ•˜๋Š” ๊ฒƒ์€ ์„ ํƒ์ด ์•„๋‹Œ ํ•„์ˆ˜์ด๋‹ค.

  • useEffect์˜ Effect ํ•จ์ˆ˜ ๋‚ด๋ถ€์˜ ๋ชจ๋“  React ๊ฐ’๋“ค์€ ์ข…์†์„ฑ ๋ฐฐ์—ด์— ํฌํ•จ๋˜์–ด์•ผ ํ•œ๋‹ค.

  • ์ข…์†์„ฑ์„ ๋ณ€๊ฒฝํ•˜๊ธฐ ํ•˜๋ ค๋ฉด ์ฝ”๋“œ๋ฅผ ๋จผ์ € ๋ณ€๊ฒฝํ•˜๋Š” ๊ฒƒ์ด ์ข‹๋‹ค

Last updated