[React AtoZ] `useEffect`와 의존성 배열, 그리고 EsLint

kwonowk·2025년 5월 24일

React AtoZ

목록 보기
2/2
post-thumbnail

React에서 useEffect를 쓸 때 가장 많이 부딪히는 부분 중 하나가 의존성 배열(dependency array) 설정입니다. 적게 넣으면 버그 나고, 많이 넣으면 무한루프... 😵

이 글에서는 useEffect와 의존성 배열을 언제, 어떻게 설정해야 하는지 완전 정리해봅니다.


useEffect란?

useEffect컴포넌트가 렌더링된 이후에 실행되는 side-effect 처리 함수입니다. React의 선언적 UI 렌더링 흐름과 별개로, 아래와 같은 "부수효과" 작업에 사용합니다.:

  • API 요청
  • DOM 조작
  • 이벤트 등록
  • 타이머 설정 등…

즉, 리액트의 순수 렌더링 외의 모든 부가 작업을 처리합니다.

useEffect(() => {
  // 여기가 side effect 영역
}, [deps]);

의존성 배열이란?

의존성 배열(dependency array)은 useEffect의 재실행 조건을 정의하는 역할을 합니다.

의존성 배열실행 시점
생략모든 렌더링 후 실행
[]마운트 시 1회만 실행
[a, b]a나 b가 변경될 때만 실행

예시

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

count가 바뀔 때만 effect 실행


useEffect의 실행 순서

렌더링 → DOM 업데이트 → useEffect 실행

  • 이 말은 곧, useEffect는 화면이 바뀐 뒤 실행된다는 뜻입니다.
  • useEffect는 비동기적으로 실행되며, 렌더링을 막지 않습니다.

useLayoutEffect와의 차이점?

  • useLayoutEffect: DOM 업데이트 이전에 실행됨 (동기)
  • useEffect: DOM 업데이트 이후에 실행됨 (비동기)

클린업 함수란?

useEffect는 필요에 따라 정리(clean-up) 작업을 할 수 있습니다. 예를 들어 이벤트 리스너 제거, 타이머 정리 등:

useEffect(() => {
  const handler = () => console.log("scroll");
  window.addEventListener("scroll", handler);

  // 컴포넌트가 unmount 되거나, 의존성이 바뀔 때 실행됨
  return () => {
    window.removeEventListener("scroll", handler);
  };
}, []);

클린업 함수는 다음 시점에 실행됩니다:

  • 컴포넌트가 언마운트될 때
  • 의존성 배열이 바뀔 때 → 기존 effect 정리 후 새로운 effect 실행

⚠️ 의존성 배열, 왜 중요할까?

👍 올바르게 설정 시

  • 불필요한 재실행 방지 → 성능 최적화
  • 최신 데이터 값 사용 → 정확한 로직 보장

👎 잘못 설정 시

  • 무한 루프 발생(setState → 렌더링 → effect → 다시 setState ...)
  • stale data(오래된 값) 사용 → 예측 불가한 버그 발생

의존성 배열 설정 원칙

Effect 내부에서 사용하는 모든 외부 변수(state, props, 함수 등)는 반드시 의존성 배열에 포함해야한다.

useEffect(() => {
  console.log(user.name); // user 사용
}, [user]); // 반드시 user를 넣어야 함

ESLint가 도와주는 이유

  • React 팀에서 만든 공식 ESLint 플러그인
  • 코드의 문법 오류, 버그 가능성, 스타일 문제를 미리 찾아주는 도구
    • React 프로젝트에서는 보통 eslint-plugin-react-hooks라는 플러그인을 함께 사용
    • React 훅의 사용 규칙, 특히 useEffect, useCallback, useMemo 같은 훅의 의존성 배열을 자동으로 검사한다.

예시

useEffect(() => {
  console.log(count); // 사용 중
}, []); // ❌ ESLint 경고!

➡️ count를 사용했는데 의존성 배열에 없음 → stale data 위험!


해결

useEffect(() => {
  console.log(count);
}, [count]); // ✅ 최신 값 동기화 보장

ESLint 경고, 무시해도 될까?

가끔은 무시해도 됩니다. 정말 필요한 경우에만!

예: 마운트 시 단 1회만 실행

useEffect(() => {
  fetchData(); // 내부에서 state를 사용하지 않는다면 괜찮음
}, []);

함수와 객체는 왜 주의해야 할까?

1. 함수

  • 자바스크립트에서 함수와 객체는 참조값이 중요 !
    const obj = { name: "Alice" };
    const another = { name: "Alice" };
    console.log(obj === another); // false
    ➡️ 내용이 같아도, 객체나 함수는 새로 만들면 다른 것으로 간주된다.
    ➡️ 즉, 매번 새로 생성되면 이전 값과 비교해서 '변했다'고 판단!

예시 코드

function App() {
  const fetchData = () => {
    console.log("데이터 요청");
  };

  useEffect(() => {
    fetchData();
  }, [fetchData]);
}

➡️ 위 코드는 매 렌더링마다 fetchData 함수가 새로 만들어지기 때문에 useEffect매번 실행됩니다. ⛔


해결 방법: useCallback

  • useCallback : 함수의 참조값을 고정해주는 훅이다.

    const fetchData = useCallback(() => {
      console.log("데이터 요청");
    }, []);

    ➡️ 이제 fetchData는 참조값이 고정되므로, useEffect도 안정적으로 동작합니다.


2. 객체

  • 객체 또한 렌더링마다 새로 생성된다.
    useEffect(() => {
      console.log("옵션 변경됨");
    }, [{ size: 10 }]); // 매번 다른 객체로 인식됨!

해결 방법: useMemo

  • useMemo는 객체를 처음 한 번만 생성하고, 의존성 배열이 변하지 않는 한 재사용합니다.

    const options = useMemo(() => ({ size: 10 }), []);
    
    useEffect(() => {
      console.log("옵션 변경됨");
    }, [options]);

    ➡️ 객체 참조를 고정해서 useEffect가 불필요하게 실행되지 않도록 합니다.


요약 정리

상황의존성 배열에 넣어야 할까?해결 방법
내부에서 state, props 사용✅ 예배열에 직접 포함
함수 사용⚠️ 항상 새로운 참조useCallback으로 감싸기
객체/배열 사용⚠️ 새로 생성됨useMemo로 감싸기
마운트 시 1회 실행[]클린한 로직 보장해야 함
ESLint 경고 무시⛔ 가급적 피하기주석으로 의도 설명하기

마무리

useEffect는 단순해 보이지만, 의존성 배열을 잘못 쓰면 예측 불가능한 버그로 이어질 수 있습니다. 다음 원칙만 기억하기:

  • useEffect 안에서 쓰는 모든 변수는 의존성 배열에 넣기
  • 함수나 객체는 useCallback / useMemo로 감싸기
  • 불필요한 ESLint 무시는 금물

profile
동균이다

0개의 댓글