9. 렌더 페이즈의 업데이트
이번 챕터에선 리액트 렌더링 중 setState 호출 시 업데이트가 임시 저장되고 소비되는 내부 메커니즘을 파헤칩니다.
1. 동작 원리 및 최적화
idle 상태에서와는 다르게 Work가 진행 중인 상태에서 setState가 호출되면, React는 다음과 같은 방식으로 처리합니다.
- Work 등록 생략: 이미 렌더링이 진행 중이므로 새로운 스케줄링(
scheduleUpdateOnFiber)을 하지 않습니다. - 즉각적인 소비: 발생한 업데이트는 다음 '패스(Pass)'에서 즉시 소비됩니다.
- 재귀 제한: 무한 루프 방지를 위해
RE_RENDER_LIMIT = 25상수를 사용하여 재호출 횟수를 엄격히 제한합니다.
2. 업데이트 저장 메커니즘
enqueueRenderPhaseUpdate 함수에서 업데이트 객체의 링크드 리스트 구조로 저장하는 방식을 확인할 수 있습니다.
javascript
// /react/packages/react-reconciler/src/ReactFiberHooks.js
function enqueueRenderPhaseUpdate<S, A>(queue: UpdateQueue<S, A>, update: Update<S, A>): void {
...
const pending = queue.pending;
if (pending === null) {
// 2. 첫 업데이트라면 자신을 가리키는 순환 구조 생성
update.next = update;
} else {
// 3. 기존 업데이트가 있다면 링크드 리스트로 연결 (Circular Linked List)
update.next = pending.next;
pending.next = update;
}
queue.pending = update;
}업데이트를 소비할 때는 가장 오래된 것부터 순서대로 계산해야 하기 때문에 pending.next부터 시작해서 고리를 따라가며 상태를 업데이트합니다.
3. 업데이트 소비 과정 및 25회 제한 루프
컴포넌트가 처음 호출된 후, 만약 내부에서 setState가 발생했다면 React는 아래 루프에 진입합니다.
javascript
// /react/packages/react-reconciler/src/ReactFiberHooks.js
function renderWithHooksAgain(workInProgress, Component, props, secondArg) {
let numberOfReRenders = 0;
let children;
do {
// 1. 현재 패스의 업데이트 플래그 초기화
didScheduleRenderPhaseUpdateDuringThisPass = false;
// 2. 무한 루프 체크 (25회 제한)
if (numberOfReRenders >= RE_RENDER_LIMIT) {
throw new Error('Too many re-renders. React limits the number of renders to prevent an infinite loop.');
}
numberOfReRenders += 1;
// 3. 훅 상태 초기화 (처음부터 다시 계산하기 위함)
currentHook = null;
workInProgressHook = null;
// 4. 디스패처를 'Rerender' 전용으로 교체
ReactSharedInternals.H = HooksDispatcherOnRerender;
// 5. 컴포넌트 재호출
children = Component(props, secondArg);
// 6. 컴포넌트 실행 중 다시 setState가 불렸다면(flag가 true가 됨) 루프 반복
} while (didScheduleRenderPhaseUpdateDuringThisPass);
return children;
}