본문 바로가기
React-study/presentation

[발표] useEffect와 useLayoutEffect

by 어느새벽 2024. 6. 4.

useEffect는 컴포넌트를 외부 시스템과 동기화할 수 있는 리액트의 훅으로 구조는 다음과 같다.

useEffect(setup, dependencies?)

 

setup : 흔히 화살표함수로 할당하는 셋업함수이다.

 

dependencies : 흔히 [] 로 useEffect 훅이 다시 실행될지 여부를 결정한다.

 

 1. 기본사용 :  아래 예제에서 useEffect는 count가 변경될 때마다 실행된다. 의존성 배열 안에 count 확인.

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

function MyComponent() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    console.log('count가 변경될 때마다 실행됩니다.');
  }, [count]); // count가 의존성 배열에 포함됨

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
}

 

 2. 빈 배열 사용 : 빈배열 []을 의존성 배열로 전달하면 useEffect는 컴포넌트가 처음 렌더링될 때 한번만 실행된다. 이후에는 다시 실행되지 않는다. 

import React, { useEffect } from 'react';

function MyComponent() {
  useEffect(() => {
    console.log('컴포넌트가 처음 렌더링될 때 한 번만 실행됩니다.');
  }, []); // 빈 배열

  return <div>Hello, world!</div>;
}

 

 3. 의존성 배열 생략 : useEffect는 컴포넌트가 렌더링될 때마다 (즉, 상태나 속성 값이 변경될 때마다) 실행된다. 

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

function MyComponent() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    console.log('상태나 속성 값이 변경될 때마다 실행됩니다.');
  }); // 의존성 배열 없음

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
}

 

 4. 여러 의존성 사용 : count와 text가 변경될 때마다 useEffect가 실행된다. 의존성 배열에 두 상태가 모두 포함되었기 때문이다.

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

function MyComponent() {
  const [count, setCount] = useState(0);
  const [text, setText] = useState('');

  useEffect(() => {
    console.log('count 또는 text가 변경될 때마다 실행됩니다.');
  }, [count, text]); // count와 text가 의존성 배열에 포함됨

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
      <input value={text} onChange={(e) => setText(e.target.value)} />
    </div>
  );
}

 

주의사항

  1. 훅이므로 컴포넌트의 최상위 레벨 또는 자체 훅에서만 호출할 것
  2. 외부 시스템과 동기화하려는 목적이 아니려면 굳이 사용 안해도 됨
  3. Strict 모드가 켜져 있으면 첫번째 실제 셋업 전에 개발전용의 셋업과 클린업 사이클을 한번 더 실행함. 문제가 발생하면 클린업 기능을 구현해야 함.
  4. 의존성에 자주 사용되는 객체 또는 함수가 있는 경우 Effect가 필요 이상으로 실행될 위험이 있음.
  5. Effect가 상호작용 예를들면 클릭으로 인한 것이 아니라면 리액트는 브라우저가 Effect를 실행하기 전에 업데이트된 화면을 먼저 그림. Effect가 시각적인 작업 예를들면 툴팁 위치 지정을 하고 있고 지연이 눈의 띄는 경우 예를들어 깜박임이 있으면 useLayoutEffect로 대체 해야 함. 예시로 클릭으로 인해 Effect가 발생한 경우에도 브라우저는 Effect 내부의 state 업데이트를 처리하기 전에 화면을 다시 그릴 수 있다. 만약 브라우저가 화면을 다시 칠하지 못하도록 차단해야 하는 경우라면 useLayoutEffect로 바꿔야 함.
  6. Effects는 클라이언트에서만 실행된다. 서버 렌더링 중에는 실행되지 않는다.

클린업 함수란?

클린업 함수(cleanup function)는 useEffect 훅에서 반환하는 함수로, 사이드 이펙트가 발생한 이후에 이를 정리(clean up)하는 역할을 한다. 아래는 예시이다.

 

1. 이벤트 리스너 정리 : 아래 예제에서 useEffect는 컴포넌트가 처음 렌더링될때 resize 이벤트 리스너를 추가한다. 클린업 함수는 컴포넌트가 언마운트 될 때 resize 이벤트 리스너를 제거한다. 이를 통해 메모리 누수를 방지 할 수 있다.

import React, { useEffect } from 'react';

function MyComponent() {
  useEffect(() => {
    const handleResize = () => {
      console.log('윈도우 크기가 변경되었습니다.');
    };

    window.addEventListener('resize', handleResize);

    // 클린업 함수 반환
    return () => {
      window.removeEventListener('resize', handleResize);
    };
  }, []); // 빈 배열, 처음 렌더링될 때 한 번만 실행

  return <div>Hello, world!</div>;
}

 

2. 타이머 정리 : useEffect는 1초마다 count를 증가시키는 타이머를 설정한다. 클린업 함수는 컴포넌트가 언마운트될 때 타이머를 정리한다. 이를 통해 타이머가 계속 실행되지 않는다.

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

function TimerComponent() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    const interval = setInterval(() => {
      setCount((prevCount) => prevCount + 1);
    }, 1000);

    // 클린업 함수 반환
    return () => {
      clearInterval(interval);
    };
  }, []); // 빈 배열, 처음 렌더링될 때 한 번만 실행

  return <div>Count: {count}</div>;
}

 

3. 의존성 배열이 있는 경우의 클린업 : useEffect는 url이 변경될 때마다 데이터를 가져온다. 클린업 함수는 isMounted 플래그를 false로 설정하여 컴포넌트가 언마운트되었거나 url이 변경되어도 이전의 비동기 작업이 상태를 업데이트하지 않도록 한다. 

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

function DataFetcher({ url }) {
  const [data, setData] = useState(null);

  useEffect(() => {
    let isMounted = true;

    fetch(url)
      .then((response) => response.json())
      .then((data) => {
        if (isMounted) {
          setData(data);
        }
      });

    // 클린업 함수 반환
    return () => {
      isMounted = false;
    };
  }, [url]); // url이 변경될 때마다 실행

  return <div>Data: {JSON.stringify(data)}</div>;
}

 

클린업 함수는 리소스를 효율적으로 관리하고 불필요한 작업을 방지하며, 메모리 누수를 예방하는데 매우 유용하다.

useEffect가 다시 실행되거나 컴포넌트가 언마운트될 때 호출되므로 이러한 작업을 안전하게 수행할 수 있다.

 

useEffect 사용하는 시기

  1. 페이지가 처음 열릴 때 : 예를들어 웹페이지가 처음 열리면 데이터를 서버에서 가져와 화면에 표시하고 싶을 때 사용한다. 
  2. 값이 변경될 때 : 사용자가 어떤 값을 입력하면 그 값이 변경될 때마다 특정 작업을 하고 싶을 때 사용한다.
  3. 화면에서 사라질 때 : 화면을 떠나기 전에 데이터를 저장하거나 남은 작업을 정리하고 싶을 때도 사용할 수 있다.

화면에서 사라진다는 것은?

화면에서 사라진다는 건, 웹페이지에서 어떤 컴포넌트가 더 이상 보이지 않게 되는 상황을 말한다. 컴포넌트는 리액트에서 페이지의 특정 부분을 담당하는 작은 조각이라고 생각하면 된다. 예를 들어, 버튼이나 텍스트 박스 같은 것들이 컴포넌트이다.

 

왜 화면에서 사라질 때 무언가를 해야 하는가?

컴포넌트가 화면에서 사라질 때, 몇가지 중요한 작업이 필요할 수 있다.

  • 데이터 저장 : 사용자가 입력한 데이터를 저장하고 싶을 때
  • 정리 작업 : 사용하지 않는 메모리나 리소스를 해제해서 컴퓨터의 자원을 절약하고 싶을 때
  • 이벤트 정리 : 컴포넌트가 더 이상 필요 없는 이벤트 리스너를 제거하고 싶을 때

useEffect에서 화면에서 사라질 때의 작동 방식

useEffect는 컴포넌트가 화면에 나타날 때 뿐만 아니라 사라질 때도 실행할 수 있다. 이를 클린업 함수라고 한다.

 

useLayoutEffcet란?

화면이 그려진 후(렌더링 후) 실행되는 useEffect와 달리 화면에 그려지기 전에 무언가를 하고 싶을 때 사용된다.

 

각각 왜 사용할까?

useEffect : 비동기 작업이나 타이머 등, 화면에 바로 반영될 필요가 없는 작업을 할 때 사용한다.

useLayoutEffect : 레이아웃이나 스타일 등, 화면에 바로 반영해야 하는 작업을 할 때 사용한다.