당신의 useEffect, 안녕하신가요?

이가 은·2025년 11월 8일

react

목록 보기
6/9
post-thumbnail

useEffect 가이드

React 공식 문서에서는 You Might Not Need an Effect글에서 useEffect의 무분별한 사용성을 경고한다.

  • ⚠️ 불필요한 상태 업데이트는 useEffect를 사용하지 않고 변수로 선언해서 사용하라.
  • ⚠️ 비용이 많이 드는 연산에서 useEffect로 또 다른 상태를 업데이트하지 말고 함수를 변수에 할당하여 사용하라. useMemo를 적절히 사용한다.
  • ⚠️ 상태 값 재설정이 필요하다면 key를 사용하여 컴포넌트를 업데이트하라.
  • ⚠️ 페이지가 표시되는 중에 사용하지 않아도 되는 로직이라면 이벤트 핸들러에 위임하라.
  • ⚠️ 한 번만 실행되어야하는 로직이라면 useEffect 내부에 조건 변수를 추가하여 함께 사용하라. 의존성 배열이 비어있더라도 다른 로직으로 중복 렌더링이 될 가능성이 높다.

useEffect, 안녕하신가요?

나는 useEffect를 잘 사용하고 있을까?

얼마 안 되는 경력 중 useEffect와 싸운 날들이 많다.
그 중 최근에 useEffect를 사용하면서 겪은 아하! 모먼트를 적어본다.

기존 코드에 useEffect 의존성 배열에 상태값이 두 개 있었다.
view가 그려진 후 한 번 동작하는 기능이 필요했다.
그래서 useEffect가 적합했고, 나는 의존성 배열이 비어있는 useEffect를 추가했다.

useEffect(() => {
  // ...
}, [])

추가하고나니 마음에 안들었다.
왜 나는 이 useEffect가 마음에 안들었을까?

리렌더링 악몽

과거에 React를 제대로 학습하지 못하고 무자비하게 사용한 useEffect로 연쇄 리렌더링 악몽을 겪었다.
코드는 너무 길고 비즈니스 로직과 상태, useEffect가 강결합되어 코드 파악하는데 한 세월을 보냈다. useEffect가 난무하는 상황을 진정시키고 이벤트 핸들러로 어떻게든 해결하려고 했던 추억(?)이 있다.

초기 렌더링 사용 목적이 아니라면 useEffect 의존성 배열에 추적하는 상태 값을 명시해줘야 한다.
만약 이 때 사용하는 상태 값이 여러개라면?
의존성 배열의 모든 상태값이 각각 다른 비즈니스 로직에 연관이 되어있다면?

useEffect(() => {
  // a 상태값 연관 로직
  // b 상태값 연관 로직
  // c 상태값 연관 로직
  // ...
}, [a, b, c, d, ...])

물론 위의 경우, 관심사를 분리하면 문제가 되지 않는다.
하지만 모든 개발자가 그렇게 작업하진 않는다.
React에서 경고한 것처럼 상태 변화시 리렌더링 === useEffect 라고 생각하는 개발자들도 많다.

위와 같은 악몽으로,
useEffect를 여러번 사용한 찝찝함이 나를 괴롭혔다.


더 좋은 방법은?

useEffect를 여러번 사용하는 것을 지양할 필요는 없다.
React는 useEffect를 외부 환경과 동기화하려고 만들었다. api서버, 브라우저, 라이브러리 등등.. 물론 기존의 클래스 컴포넌트의 불편함을 보완하려고 한 부분도 있다.

React가 이렇게 잘 만들어줬는데 잘 써줘야지. 근데 그래도 찝찝해.
그래서 동료에게 코드 리뷰를 받았다.

찝찝함을 덜어낼 수 있는 방법 몇 가지를 전수받았다.

useEffect말고 useRef사용하기

useEffect말고 useRef로 특정 조건을 추가한 방법이다.

장점: useEffect 쓰지 않아도 된다.
단점: 비동기 처리에 필요한 로직은 예상치 않은 동작이 있을 수 있다. 동작 후 cleanup이 불가능하다.

function Component() {
  const isFirstRender = useRef(true);

  // 렌더링 될 때마다 실행되지만, 조건문으로 제어
  if (isFirstRender.current) {
    isFirstRender.current = false;
    
    // 초기 렌더링 로직
    ...
  }

  return <div>Content</div>;
}

재사용성을 높혀 커스텀 훅으로 분리

useRef나 useEffect를 그대로 활용하고 관심사를 분리하여 재사용성을 높힌 방법이다.

장점: 내부 로직과 데이터가 캡슐화 된다. 테스트에 용이하다.
단점: 내부 의존성 추적이 어렵다. 코드가 드러나지 않아 의도가 불명확하다.

export function useFirstRenderEffect(callback: () => void) {
  const hasRun = useRef(false);
  
  if (typeof window !== 'undefined' && !hasRun.current) {
    callback();
    hasRun.current = true;
  }
}

// 사용
function ProductPage() {
  useFirstRenderEffect(() => {
    // 초기 렌더링 로직
    ...
  });
  
  return <div>{contents}</div>;
}

선언적으로 useEffect 사용하기

useEffect를 그대로 가져와서 선언적 컴포넌트로 만드는 방법이다.

장점: React 철학과 구조가 맞아 선언적이다.
단점: 컴포넌트인데 UI를 그리지 않는다. 의미 없는 노드를 생성한다. 훅이 더 나을지도..

function Mounted({ callback }: { callback: () => void }) {
  useEffect(() => {
    // 초기 렌더링 로직
    ...
  },[])
  return null
}

// 사용
return (
  <>
  	<Mounted callback={function} /> // 명시적으로 사용
  	<Header />
  	<Body />
  	<Footer />
  </>

웰컴백

제시한 방법 중 선택해서 사용하면 내 찝찝함을 덜어낼 수 있을 것이라 생각했다.
하지만 고민하고 좀 더 하고나서, 아무것도 채택하지 않았다.

세 번째 방법이 제일 매력적이게 다가왔다.
Provider처럼 UI를 반환하지 않는 래퍼 컴포넌트들도 있으니 충분히 사용할 만 했고 다른 컴포넌트에서 재사용하는 로직이다보니 명시적인게 좋아보였다.

하지만 나는 다시 고민했다.

단순히 선호하지 않는다는 이유로 오버엔지니어링을 선택하려는건 아닌가?

작업 중인 컴포넌트에는 이미 커스텀훅으로 관심사 분리가 어느 정도 되어있었다.
useEffect가 몇 개 더 있다고 내부 상태들이 의존성에 얽혀 유지보수가 어려워 질 것 같지 않다.

내가 선호하지 않는 이유는 뭘까? 가독성 떄문에? 그저 지나간 과거의 악몽때문에?

대안들을 살펴보고 다시 보니 굳이? 라는 생각도 스쳐지나갔다.

그래서 초기 렌더링시 특정 동작을 하는 useEffect를 그 자리에 두었다.
해당 코드를 고치는 일은 없었다.

만약 다음에...

미래에 useEffect가 더 복잡한 로직과 의존성 배열로 골치 아파지는 일이 생긴다면, 그때는 선언적 컴포넌트를 활용해보자.
필요할 때 아래와 같이 여러 곳에서 활용할 수 있을 것이다.

  • 전역 설정이 필요할 때 (에러 바운더리, 리다이렉트, 동적 스타일 등등)
  • 여러 페이지에서 재사용되는 공통 기능 (커스텀 훅도 가능)
  • 특정 기능을 조건에 따라 명시적으로 사용할 때
profile
독서와 개발을 좋아하는 프론트 개발자 🍀

5개의 댓글

comment-user-thumbnail
2025년 11월 9일

useEffect.... 저도 사용하다가 예측하기 어려운 버그를 한두번 겪은게 아니라 최대한 사용안하고 있어요..
팀원들이랑 설계하다가 effect 써야되나 하면 다들 한숨부터 쉬심.. ㅋㅋㅋ
그래도 사용해야되는 경우는 있고 복잡한 effect 동작이라고하면 저도 항상 두번째, 세번째 방법을 사용하고 있어요!

1개의 답글
comment-user-thumbnail
2025년 11월 9일

넘나 좋은글이다... 잘읽었어요 👍

답글 달기
comment-user-thumbnail
2025년 11월 10일

오.. 선언적으로 useEffect 사용하는 방법은 처음봤는데 생각보다 잘 활용할 수 있을거같은데요

답글 달기