useEffect dependency array는 effect 내부에서 사용하는 props, state, 함수 같은 reactive 값을 React에게 알려주는 역할을 합니다. 해당 값이 바뀌면 effect가 다시 실행됩니다.
빈 배열을 넣으면 mount 시점에 한 번 실행되는 형태가 되고, 배열을 생략하면 매 render 후 실행됩니다. dependency를 빼서 실행을 억지로 막기보다, effect가 실제로 의존하는 값을 넣고 구조를 조정하는 게 안전합니다.
꼬리질문
조금 더 깊게 물어본다면
답변 뒤에 이어질 수 있는 질문들을 하나씩 열어볼 수 있어요.
dependency array에서 값을 빼면 왜 stale closure가 생기나요?
React 컴포넌트는 render마다 새로운 함수 실행 컨텍스트를 만듭니다. effect callback은 그 render 시점의 값을 닫아둡니다. dependency에 값을 넣지 않으면 새 render의 값으로 effect가 갱신되지 않아서, 예전 render의 값을 계속 볼 수 있습니다.
eslint exhaustive-deps 경고는 무조건 따라야 하나요?
대부분은 따르는 편이 안전합니다. 경고가 불편하다면 dependency를 지우기보다 effect가 너무 많은 일을 하고 있는지, 함수나 값을 effect 바깥으로 옮길 수 있는지, state updater 형태를 쓸 수 있는지 먼저 봐야 합니다.
빈 배열은 componentDidMount와 완전히 같나요?
비슷하게 쓰일 수는 있지만 완전히 같은 개념은 아닙니다. React의 effect는 render 이후에 실행되고, Strict Mode 개발 환경에서는 mount effect가 두 번 실행되는 것처럼 보일 수도 있습니다. 그래서 “딱 한 번만 실행된다”라고 단정하면 위험합니다.
함수가 dependency에 들어가면 계속 effect가 반복될 수 있는데 어떻게 하나요?
함수가 render마다 새로 만들어지면 dependency가 매번 바뀐 것으로 보일 수 있습니다. 이때는 함수가 effect 안에서만 필요하다면 effect 내부로 옮기고, 외부에서도 필요하다면 useCallback으로 reference를 안정화할 수 있습니다. 다만 useCallback을 무조건 붙이는 게 아니라 실제 의존 관계를 먼저 정리해야 합니다.
dependency를 줄이고 싶을 때는 어떻게 해야 하나요?
dependency를 임의로 빼기보다 effect가 너무 많은 책임을 갖고 있는지 먼저 봐야 합니다. 서로 다른 동기화 작업이 한 effect 안에 섞여 있다면 effect를 나누는 편이 낫습니다.
또 최신 state를 기준으로만 값을 갱신하면 되는 경우에는 state updater 형태를 쓰고, effect 안에서만 필요한 함수는 effect 내부로 옮겨 dependency를 단순하게 만들 수 있습니다.
부가 설명
dependency array를 “몇 번 실행할지 정하는 옵션”으로만 보면 실수하기 쉽습니다. effect 안에서 어떤 state를 읽고 있는데 dependency에서 빼면, effect는 예전 값을 계속 바라보는 stale closure 문제를 만들 수 있습니다.