[Hook] useEffect

OlMinJe·2025년 9월 2일

React

목록 보기
7/19

리액트 공식 문서를 참고한 정리 내용 (25.08 기준)

외부 시스템과 컴포넌트를 동기화하는 Hook이다.

useEffect(setup, dependencies?)

컴포넌트의 최상위 레벨에서 useEffect를 호출하여 Effect를 선언할 수 있다.

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

function ChatRoom({ roomId }) {
  const [serverUrl, setServerUrl] = useState('https://localhost:1234');

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

매개변수

setup(설정)

  • 여기에 실제로 하고 싶은 Effect 로직을 적는다.(ex: 데이터 가져오기, 이벤트 등록하기, DOM 수정하기 등)
  • 이 함수 안에서정리(clean-up) 함수를 선택적으로 반환할 수도 있다.

React는

  • 처음 컴포넌트가 화면에 붙을 때 → setup 실행
  • 의존성이 바뀔 때 → 이전 clean-up 실행 후, 새로운 setup 실행
  • 컴포넌트가 사라질 때 → 마지막으로 clean-up 실행

👉 setup은 "시작할 때 실행할 작업", cleanup은 "정리할 때 실행할 작업"이다.

dependencies(선택사항)

  • setup 함수 안에서 쓰이는 props, state, 변수, 함수를 배열로 나열한다. 이때, React는 이 배열을 보고 값이 바뀌었을 때만 Effect를 다시 실행한다.
  • 배열은 항상 고정된 길이여야 하고 [a, b, c] 형태로 적어야 하며, 비교는 Object.is 방식(===와 거의 비슷)으로 진행한다.

👉 만약 의존성 배열을 생략하면, Effect는 컴포넌트가 리렌더링될 때마다 무조건 실행된다. (어떤 값이 바뀔 때만 다시 실행할지 지정하는 리스트이다.)


반환값

useEffectundefined를 반환한다.


주의사항

1. 위치 규칙
useEffect는 Hook이라서 컴포넌트의 최상위나 커스텀 Hook에서만 사용할 수있다.
반복문, 조건문 안에는 당연히 사용할 수 없으며, 필요한 경우에는 로직을 새로운 컴포넌트로 빼서 사용해야 한다.

2. 꼭 필요할 때만 사용하기
외부 시스템(DOM, 네트워크, 이벤트 등)과 동기화할 필요가 없으면 useEffect 자체가 필요없을 수 있으며, 단순히 상태 관리나 계산만 하면 useEffect 사용을 지양한다.

3. Strict Mode
개발 모드에서만 테스트용으로 정리(clean-up) 함수가 제대로 동작하는지 확인해기 위해 Effect를 두 번 실행할 수 있다.
이로인해 정리(clean-up) 함수를 잘 작성해야 예기치 않은 문제를 막을 수 있다.

4. 의존성 배열 주의
만약 의존성이 객체나 내부 함수라면 렌더링마다 새로 만들어지기 때문에 Effect가 필요 이상으로 실행될 수 있다.

이를 해결하기 위해서는

  • 불필요한 객체/함수 의존성 없애기
  • useCallback, useMemo로 함수/객체를 메모이제이션
  • 아니면 로직을 useEffect 밖으로 빼내기

5. 실행 타이밍
사용자 사요작용이 아닌 경우(ex. 네트워크 요청, DOM 업데이트 등)

  • React는 화면을 먼저 그리고 나서 Effect를 실행하며, 이로 인해 깜빡임이 생기면 useLayoutEffect로 바꿔야 한다.

사용자 상호작용으로 발생한 경우(ex. 클릭 이벤트)

  • React는 이벤트 시스템이 Effect의 결과를 볼 수 있게 화면을 그리기 전에 Effect를 실행할 수 있다.
  • 단, alert() 같은 메서드는 화면을 그린 후에 실행해야 되니 setTimeout으로 감싸는게 안전하다.

어떤 경우엔 상호작용이어도 React가 먼저 화면을 그리게 둘 수도 있으며, 이때도 깜빡임이 문제라면 useLayoutEffect를 사용한다.

6. 환경 제약
useEffect는 클라이언트에서만 실행되기 때문에, 서버 사이드 렌더링(SSR) 단계에서는 실행되지 않는다.

요약

  • useEffect는 컴포넌트 최상위에서만 사용
  • 외부 시스템과 연결할 때만 필요
  • Strict Mode에서 두 번 실행될 수 있음
  • 의존성 배열은 신중히 관리
  • 화면 깜빡임이 문제면 useLayoutEffect 고려
  • 브라우저에서만 실행된다는 점을 기억하면 된다.

사용법

외부 시스템과 연결

외부 시스템과 연결하려면 컴포넌트의 최상위 레벨에서 useEffect를 호출해야 한다.

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

function ChatRoom({ roomId }) {
  const [serverUrl, setServerUrl] = useState('https://localhost:1234');

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

useEffect는 마운트 시 설정 함수를 실행하고, 의존성 변경 시 이전 정리 후 새 설정을 실행하며, 언마운트 시 마지막으로 정리를 실행한다.

추가로 Effect는 독립된 프로세스처럼 작성해야 하며, 정리 함수는 설정 함수의 동작을 정확히 되돌릴 수 있어야 한다. 그리고 마운드, 업데이트, 언마운트 상황에 상관없이 안정적으로 동작해야 한다.

정리하자면, **useEffect는 외부 시스템과의 연결/해제를 관리하며, 정리 함수가 설정을 완벽히 되돌릴 수 있도록 작성하는 것이 핵심**이다.

⭐ Effect는 (소켓 통신과 같은) 외부 시스템과 컴포넌트가 동기화를 유지할 수 있도록 해야한다.
외부시스템 예시:

  • setInterval()에 의해 관리되는 타이머 또는 clearInterval()
  • window.addEventListener()을 이용한 이벤트 구독 또는 window.removeEventListener().
  • animation.start()와 같은 서드 파티 애니메이션 라이브러리 API 또는 animation.reset().

Effect를 이용한 데이터 패칭

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]);

  // ...

Effect에서 직접 데이터 페칭 로직을 작성하면 나중에 캐싱 기능이나 서버 렌더링과 같은 최적화를 추가하기 어려워진다. 자체 제작된 커스텀 Hook이나 커뮤니티에 의해 유지보수되는 Hook을 사용하는 편이 더 간단하다.

// useData custom hook
function useData(url) {
  const [data, setData] = useState(null);
  useEffect(() => {
    if (url) {
      let ignore = false;
      fetch(url)
        .then(response => response.json())
        .then(json => {
          if (!ignore) {
            setData(json);
          }
        });
      return () => {
        ignore = true;
      };
    }
  }, [url]);
  return data;
}

// SippingForm compoennts
function ShippingForm({ country }) {
  const cities = useData(`/api/cities?country=${country}`);
  const [city, setCity] = useState(null);
  const areas = useData(city ? `/api/areas?city=${city}` : null);
  // ...

커스텀 Hook을 추출하는 것은 데이터의 흐름을 명확하게 하고, 불필요한 의존성 추가를 막을 수 있다.


Effect에서 최신 props와 state를 읽기

Effect에서 최신 props와 state를 반응하지 않고 읽고 싶은 경우에는 아래와 같이 사용한다.

function Page({ url, shoppingCart }) {
  const onVisit = useEffectEvent(visitedUrl => {
    logVisit(visitedUrl, shoppingCart.length)
  });

  useEffect(() => {
    onVisit(url);
  }, [url]); // 모든 의존성이 선언됨
  // ...
}

Effect 내에서 호출되는 코드이지만 변경에 반응하지 않기를 원한다면 useEffectEvent Hook을 사용하여 Effect event를 선언하고 shoppingCart를 읽는 코드를 그 안에 이동시키면 된다.
이 Hook은 비 반응형 코드(Effect event 로직은 최신 props와 state를 읽을 수 있음)를 배치할 수 있다.

profile
큐트걸

0개의 댓글