effect와 useEffect

Soyeon·2025년 3월 24일
1
post-thumbnail

React에서 useEffect를 정말 많이 사용하는 것을 볼 수 있다. 개념은 알았지만 "이게 왜 생겼는지", "무작정 써도 되는지" 알기 위해 공부한 내용을 정리해보도록 한다.

먼저 리액트의 Effect에 대해 알아보자.

React는 렌더링이 순수 함수처럼 예측 가능하게 이뤄져야 한다는 철학을 갖고 있다.

  • 순수 함수?
    같은 입력이 들어오면, 항상 같은 출력을 내고 함수 내부나 외부의 상태를 변경하지 않는 함수라고 한다.

그러나, 렌더링 과정에서 side effect가 발생하면 예측 불가능하고 관리가 어려워진다.

  • side effect는?
    렌더링 외에 발생하는 모든 작업을 의미한다.
    React의 주요 목적은 입력에 따라 화면 UI를 선언적으로 그리는 것인데, 컴포넌트에서 이런 순수한 UI 작업 외에도 다른 작업이 필요할 수 있다.

    예를 들어,

    • API 요청 - 서버에서 데이터 가져오기
    • 외부 이벤트 등록/제거 - scroll, resize, ...
    • 타이머 설정 - setTimeout, setInterval
    • 로컬스토리지 접근
    • 전역 상태 연동
    • 콘솔 출력 - 디버깅

따라서, React는 부작용(side effect)을 처리하는 영역을 useEffect로 분리해서 다룬다.


useEffect

화면에 그려진 후 실행되는 side effect 처리 함수다.
이는 side effect를 안전하게 처리하고, 필요 시 정리까지 할 수 있게 도와준다. 컴포넌트의 생명주기 동안 특정 작업을 하고 싶을 때 사용한다.

다음은 useEffect의 기본 구조다.

useEffect(() => {
  // 실행할 side effect 작업
  return () => {
    
  };
}, [의존성])
  • [의존성] : 언제 useEffect를 다시 실행할지 기준이 되는 배열
  • return 문 : 컴포넌트가 unmount가 되거나 의존값이 바뀌기 전에 실행되는 정리 작업 함수, clean-up 함수라고 한다.

다음은 useEffect가 실행되는 시점을 각각 정리해보았다.

1. Mount (처음 렌더링)

컴포넌트가 처음 화면에 나타난 직후 1회 실행된다.

useEffect(() => {
  console.log('처음 렌더링 됐을 때만 실행');
}, []);

2. Update (변경될 때)

의존성 배열 [] 안의 값이 바뀔 때마다 실행된다.

useEffect(() => {
  console.log(`count가 ${count}로 바뀌었어`);
}, [count]);

3. Unmount (사라질 때)

컴포넌트가 화면에서 사라질 때 return문 으로 정리 작업을 한다.

useEffect(() => {
  const timer = setInterval(() => {
    console.log('매초 실행');
  }, 1000);

  return () => {
    clearInterval(timer); // 컴포넌트 사라질 때 정리
  };
}, []);

⚠️ 주의할 점

다음은 useEffect 를 사용하면서 버그의 원인이 되는 경우와 해결방법이다.

1. 무한 루프

상태 업데이트와 의존성 배열의 조합이 잘못되면 계속 실행되는 무한 루프가 발생한다.

useEffect(() => {
  setCount(count + 1); // 의존성에 count가 있을 경우 → 무한 루프
}, [count]);

해결 방법

  • 조건문으로 제어
  • 상태 변경이 의존성에 포함 x

2. 의존성 배열 누락

배열에 필요한 값이 빠지면 useEffect가 다시 실행이 되지 않는다. -> 버그 발생

useEffect(() => {
  fetchData(userId); // userId가 바뀌어도 이펙트가 다시 실행되지 않음
}, []); // userId 없음

해결방법

  • ESLint의 react-hooks/exhaustive-deps 규칙 사용해서 실수 막기

3. 객체/배열 의존성

객체와 배열이 의존성 배열에 들어가면, 항상 새로 생성된다.

useEffect(() => {
  console.log('실행됨');
}, [[1, 2, 3]]); // 배열은 참조값이 매번 달라져서 매 렌더마다 실행됨 

해결 방법

  • useMemo로 메모제이션 하기
  • 원시값만 쓰기

4. clean-up 함수

이벤트 리스너, 타이머, 인터벌을 등록할 경우, 언마운트 시 반드시 제거해줘야 한다. -> 제거하지 않으면, 메모리 누수와 중복 호출 발생 가능

useEffect(() => {
  window.addEventListener('resize', onResize);

  // 반드시 제거하기
  return () => {
    window.removeEventListener('resize', onResize);
  };
}, []);

5. 비동기 함수

useEffect는 동기 함수만 받을 수 있기 때문에, 비동기 함수는 useEffect 안에서 선언하면 안된다.

다음과 같이 async 함수를 직접 넘기면, async 함수는 자동으로 Promise를 반환한다. 따라서 useEffect는 Promise를 clean-up 함수로 오해할 수 있어 에러가 발생할 수 있다.

// 잘못된 코드
useEffect(async () => {
  const data = await fetch();
}, []); 

해결 방법

  • useEffect 내부에 정의해서 호출하기
// 옳은 코드
useEffect(() => {
  const fetchData = async () => {
    const res = await fetch('https://localhost/5173');
    const result = await res.json();
    console.log(result);
  };

  fetchData(); // 정의한 비동기 함수 실행
}, []);

profile
탄탄한 개발자로 살아남기🗿

0개의 댓글