Recent Posts
Recent Comments
Link
Today
Total
«   2026/10   »
일 월 화 수 목 금 토
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31
관리 메뉴

Jiseong's Web Dev blog

useEffect 남용을 줄이자. 본문

React

useEffect 남용을 줄이자.

jijiseong 2025. 3. 2. 13:04

useEffect 남용의 문제

React는 useEffect를 남용하게 쉽게 만들어뒀다는 생각이 들었다.

일반적으로 잘 못 사용하는 패턴이다.

  • react의 상태에 반응해서 실행시키고싶다.
  • 서로다른 내부 상태를 동기화 하고싶다.

왜 잘 못 되었는지, 왜 사용하면 안 되는지 이해해보자.

🙃 useEffect란?

위 문제를 해결하기 위해서는 근본적으로 useEffect가 무엇인지 알아야한다.

React useEffect
useEffect는 외부 시스템과 컴포넌트를 동기화하는 React Hook입니다.

공식 문서에 따르면 외부 시스템과 연동하는 용도라고 첫 줄에 나와있다.
예를 들면

  • 소켓 연결
  • DOM 제어
  • 전역 이벤트 리스너 등록/해제
  • 서드파티 라이브러리와 연동

등이 있다.

여기서 DOM 제어는 한 번 더 생각해야할 요소이다.
가능하면 react 상태를 통해 관리하는게 좋다.

React useRef
Ref는 일종의 탈출구입니다. 다른 컴포넌트의 DOM 노드를 수동으로 조작하면 코드를 불안정하게 만들 수 있습니다.

useEffect 잘못 된 사용과 문제점

내부 state를 서로 동기화하기 위한 useEffect

문제


function Example() {
  const [count1, setCount1] = useState(0);
  const [count2, setCount2] = useState(0);

  useEffect(() => {
   setCount2(count);
  }, [count]);

  return <button onclick={() => setCount1(cur => cur+1)}/>
}

count1과 count2는 같은 값을 가지도록 useEffect를 사용했다.

문제점 1. 불필요한 리렌더링이 일어난다.
count가 업데이트 -> 리렌더링 -> count2 업데이트 -> 리렌더링
리액트의 배치처리를 활용 못하게되어 리렌더링이 2회로 늘어난다.
지금은 간단한 예시지만, 이러한 패턴이 여러 개 쌓이게된다면 디버깅이 매우 어려워지고, 복잡한 로직이 된다.

문제점 2. 항상 같은 값이 아닐 수도있다.
count1가 업데이트 된 이후 count2가 업데이트되는 순서가 생기기때문에, 항상 같은 값이 아닐 수 있다.

해결


function Example() {
  const [count1, setCount1] = useState(0);
  const [count2, setCount2] = useState(0);

  const setCount = (value: number) => {
    setCount1(value);
    setCount2(value);
  }

  return <button onclick={() => setCount(count1 + 1)}/>
}

function Example() {
  const [count1, setCount1] = useState(0);
  const count2 = count1
  return <button onclick={() => setCount1(cur => cur+1)}/>
}

이 말고도 다양한 방법이 있을 것이다.
배치처리를 사용하는 방법, 상태 선언을 아예 지우는 방법 등 상황에 맞게 사용하자.
첫번 째 예시의 경우 React가 자동으로 배치처리하기 때문에 count1, count2상태는 동시에 업데이트된다.
따라서 리렌더링이 1회만 발생한다.

마운트 시 1회만 실행하기


function Example() {

  useEffect(() => {
    fetch('http://example.com');
  }, []);

  return <></>
}

네트워크 통신은 외부시스템과 연동이기 때문에 올바른 사용법이다.
만약, 해당 로직에 상태가 포함되어야한다면 아래처럼 의존성 배열이 추가될 것이다.

function Example() {
  const [page, setPage] = useState(0);

  useEffect(() => {
    fetch(`http://example.com?page=${page}`);
  }, [page]);

  return <button onclick={setCount}/>
}

1회만 실행 시키고싶다고해서 page를 의존성 배열에서 지우는것 보다, 리액트 라이프사이클에서 벗어나도록 컴포넌트 외부에서 let 변수를 선언하는것이 올바르다.


let isFetched = false;

function Example() {
  const [page, setPage] = useState(0);


  // ✅
  useEffect(() => {
    if(!isFetched){
      fetch(`http://example.com?page=${page}`).then(() => isFetched = true);
    }
  }, [page]);

  // ❌
  // useEffect(() => {
  //   fetch(`http://example.com?page=${page}`);
  // }, []);

  return <></>
}

의존성 배열에 정확히 값을 명시하지 않으면 잠재적인 에러를 유발할 수 있다.
지금은 간단한 예시이지만, 상태를 여러가지 쓴다면 복잡해진다.

react-hooks/exhaustive-deps 룰을 위반 했다는 경고가 발생할 것이다.
최신 상태가 아닌 오래된 상태 값을 사용하기 때문에 발생하는 경고이다.
이를 해결하기 위해 강제로 무시하도록 주석을 달게 될 것이다.
지금 당장 동작한다고해도 다른 개발자들이 본다면 lint룰을 지키기위해 리팩토링할거다.
툭 건들면 터지게 되는 코드가 될 것이다.

이외에도

이외에도 많은 상황에서 useEffect를 제거할 수 있다.

https://ko.react.dev/learn/you-might-not-need-an-effect