useEffect()가 호출되면 React는 Effect 객체를 하나 생성한다.
이 객체에는 다음 정보가 들어있다:
{
tag, // effect 타입 정보 (mount/update 등)
create, // useEffect 콜백 함수
destroy, // cleanup 함수
deps, // 의존성 배열
next // 다음 effect를 가리키는 포인터
}
모든 effect 객체들은 순환 연결 리스트(circular linked list)로 연결된다.
즉, 마지막 effect의 next가 다시 첫 번째 effect를 가리킨다.
effectA → effectB → effectC ┐
↑ │
└─────────── ┘
useEffect의 실행은 렌더링 이후(commit 이후),
브라우저 paint가 완료된 뒤 비동기적으로 실행된다.
이 타이밍은 flushPassiveEffects() → commitHookEffectListMount() 내부에서 이루어진다.
즉, 렌더링 중에는 등록만 되고, 실행은 나중에(비동기) 일어난다.
이유 | 설명 |
|---|---|
1 | effect cleanup / mount / update 를 반복적으로 순회해야 함 |
2 | head, tail 구분 없이 한 바퀴 돌면 전체 순회 가능 |
3 | effect 추가/삭제 시 구조 관리가 단순함 |
4 | React Fiber 구조와의 호환성 (한 큐에서 여러 effect 처리) |
결국 순환 구조로 만들어야 “처음~끝~처음”까지 한 번에 순회 가능해진다.
let firstEffect = fiber.updateQueue.lastEffect.next;
let effect = firstEffect;
do {
// useEffect(() => {...}) 내부 콜백 실행
effect.create();
// 다음 effect로 이동
effect = effect.next;
// 🔽 한 바퀴 돌면 종료
} while (effect !== firstEffect);
firstEffect: 리스트의 시작점
effect: 현재 순회 중인 effect 객체
effect === firstEffect: 한 바퀴를 다 돌았다는 신호 → 반복 종료
“같다”는 건 객체 참조가 동일함을 의미한다. (===)
즉, effect 변수가 다시 리스트의 시작 객체를 가리키게 되면
한 바퀴를 다 돈 것이므로 루프를 종료한다.
🌀 이 조건이 없으면 순환 연결 리스트 특성상 무한 루프가 발생한다.
effect 하나만 등록된 경우:
effectA.next = effectA
순회 시:
effectA.create() 한 번 실행
effect = effect.next → 다시 effectA
effect === firstEffect → true → 즉시 종료
✅ 한 번만 실행되고 끝난다.
렌더링 단계: useEffect 훅 호출 시 effect 객체 생성 및 등록
커밋 단계: DOM 업데이트 완료 후, effect 실행 예약
패시브 이펙트 단계: 실제 effect 콜백(create) 실행
따라서 “멈추는 이유”는 렌더링이 아니라 순환 구조의 제어 로직 때문이며,
다만 effect 리스트 자체는 렌더링 중에 만들어진다.
🔥 무한 루프 발생
effect.next는 항상 존재(순환 구조)하므로
“처음으로 돌아왔을 때 멈춘다”는 조건이 없으면 루프가 끝나지 않는다.
결과적으로 브라우저가 멈추고 CPU를 100% 점유한다.
항목 | 핵심 요약 |
|---|---|
자료구조 | useEffect는 순환 연결 리스트(circular linked list)로 관리됨 |
실행 시점 | 렌더링 후, 커밋 완료 후 비동기적으로 실행 |
반복 구조 | do...while 루프를 통해 한 바퀴만 순회 |
종료 조건 |
|
하나만 있을 때 | 한 번 실행 후 즉시 종료 |
멈추지 않으면 | 무한 루프 발생, 브라우저 멈춤 |
렌더링과의 관계 | effect 등록은 렌더링 중, 실행은 렌더링 이후 |
💡 정리
useEffect는 렌더링 중 생성된 effect 객체들을 순환 연결 리스트로 관리하고,
커밋 후 한 바퀴를 돌며 각 콜백(create)을 실행한다.“처음 effect와 같아지면 멈춘다”는 건
한 바퀴 순회를 끝냈다는 신호이며,
이 조건이 없으면 무한 루프에 빠진다.