
React에서
useEffect를 쓸 때 가장 많이 부딪히는 부분 중 하나가 의존성 배열(dependency array) 설정입니다. 적게 넣으면 버그 나고, 많이 넣으면 무한루프... 😵이 글에서는
useEffect와 의존성 배열을 언제, 어떻게 설정해야 하는지 완전 정리해봅니다.
useEffect란?useEffect는 컴포넌트가 렌더링된 이후에 실행되는 side-effect 처리 함수입니다. React의 선언적 UI 렌더링 흐름과 별개로, 아래와 같은 "부수효과" 작업에 사용합니다.:
즉, 리액트의 순수 렌더링 외의 모든 부가 작업을 처리합니다.
useEffect(() => {
// 여기가 side effect 영역
}, [deps]);
의존성 배열(dependency array)은 useEffect의 재실행 조건을 정의하는 역할을 합니다.
| 의존성 배열 | 실행 시점 |
|---|---|
| 생략 | 모든 렌더링 후 실행 |
[] | 마운트 시 1회만 실행 |
[a, b] | a나 b가 변경될 때만 실행 |
useEffect(() => {
console.log(count);
}, [count]);
→ count가 바뀔 때만 effect 실행
렌더링 → DOM 업데이트 → useEffect 실행
useEffect는 화면이 바뀐 뒤 실행된다는 뜻입니다.useEffect는 비동기적으로 실행되며, 렌더링을 막지 않습니다.useLayoutEffect: DOM 업데이트 이전에 실행됨 (동기)useEffect: DOM 업데이트 이후에 실행됨 (비동기)useEffect는 필요에 따라 정리(clean-up) 작업을 할 수 있습니다. 예를 들어 이벤트 리스너 제거, 타이머 정리 등:
useEffect(() => {
const handler = () => console.log("scroll");
window.addEventListener("scroll", handler);
// 컴포넌트가 unmount 되거나, 의존성이 바뀔 때 실행됨
return () => {
window.removeEventListener("scroll", handler);
};
}, []);
Effect 내부에서 사용하는 모든 외부 변수(state, props, 함수 등)는 반드시 의존성 배열에 포함해야한다.
useEffect(() => {
console.log(user.name); // user 사용
}, [user]); // 반드시 user를 넣어야 함
eslint-plugin-react-hooks라는 플러그인을 함께 사용useEffect, useCallback, useMemo 같은 훅의 의존성 배열을 자동으로 검사한다.useEffect(() => {
console.log(count); // 사용 중
}, []); // ❌ ESLint 경고!
➡️ count를 사용했는데 의존성 배열에 없음 → stale data 위험!
useEffect(() => {
console.log(count);
}, [count]); // ✅ 최신 값 동기화 보장
가끔은 무시해도 됩니다. 정말 필요한 경우에만!
useEffect(() => {
fetchData(); // 내부에서 state를 사용하지 않는다면 괜찮음
}, []);
const obj = { name: "Alice" };
const another = { name: "Alice" };
console.log(obj === another); // false ➡️ 내용이 같아도, 객체나 함수는 새로 만들면 다른 것으로 간주된다.function App() {
const fetchData = () => {
console.log("데이터 요청");
};
useEffect(() => {
fetchData();
}, [fetchData]);
}
➡️ 위 코드는 매 렌더링마다 fetchData 함수가 새로 만들어지기 때문에 useEffect도 매번 실행됩니다. ⛔
useCallbackuseCallback : 함수의 참조값을 고정해주는 훅이다.
const fetchData = useCallback(() => {
console.log("데이터 요청");
}, []);
➡️ 이제 fetchData는 참조값이 고정되므로, useEffect도 안정적으로 동작합니다.
useEffect(() => {
console.log("옵션 변경됨");
}, [{ size: 10 }]); // 매번 다른 객체로 인식됨!useMemouseMemo는 객체를 처음 한 번만 생성하고, 의존성 배열이 변하지 않는 한 재사용합니다.
const options = useMemo(() => ({ size: 10 }), []);
useEffect(() => {
console.log("옵션 변경됨");
}, [options]);
➡️ 객체 참조를 고정해서 useEffect가 불필요하게 실행되지 않도록 합니다.
| 상황 | 의존성 배열에 넣어야 할까? | 해결 방법 |
|---|---|---|
| 내부에서 state, props 사용 | ✅ 예 | 배열에 직접 포함 |
| 함수 사용 | ⚠️ 항상 새로운 참조 | useCallback으로 감싸기 |
| 객체/배열 사용 | ⚠️ 새로 생성됨 | useMemo로 감싸기 |
| 마운트 시 1회 실행 | ✅ [] | 클린한 로직 보장해야 함 |
| ESLint 경고 무시 | ⛔ 가급적 피하기 | 주석으로 의도 설명하기 |
useEffect는 단순해 보이지만, 의존성 배열을 잘못 쓰면 예측 불가능한 버그로 이어질 수 있습니다. 다음 원칙만 기억하기:
useEffect 안에서 쓰는 모든 변수는 의존성 배열에 넣기