7. 훅 업데이트와 dispatchAction
이번 챕터에선
useState가 업데이트를 원형 연결 리스트에 적재하고 마운트 시점의 성능 최적화에 대해 알아봅니다.
1. 단계 구분의 기준: currentlyRenderingFiber
React는 Render 단계와 Idle 단계를 구분하여 업데이트를 처리합니다.
ReactFiberHooks.js 모듈을 보면, 전역변수로 currentlyRenderingFiber가 선언되어 있습니다. 이름에서 현재 렌더링 중인 Fiber임을 알 수 있으며, useState의 반환값인 setState 함수의 내부 구현을 타고 들어가면, isRenderPhaseUpdate 함수에서 참조됩니다.
jsx
// /react/packages/react-reconciler/src/ReactFiberHooks.js
function isRenderPhaseUpdate(fiber: Fiber): boolean {
const alternate = fiber.alternate;
return (
fiber === currentlyRenderingFiber ||
(alternate !== null && alternate === currentlyRenderingFiber)
);
}
function dispatchSetStateInternal<S, A>(
fiber: Fiber,
queue: UpdateQueue<S, A>,
action: A,
lane: Lane,
): boolean {
const update: Update<S, A> = {
// ...
};
if (isRenderPhaseUpdate(fiber)) {
// render phase update
} else {
// idle update
}
};이 함수는 렌더링 단계와 유휴 단계에 따라 처리 방식이 달라 둘을 구별하기 위한 목적으로 사용됩니다.
- 렌더링 단계:
currentlyRenderingFiber가 비어있지 않음 - 유휴 단계:
currentlyRenderingFiber가 비어있음
2. 업데이트 적재: 원형 연결 리스트
업데이트가 발생하면 리액트는 enqueueRenderPhaseUpdate를 통해 업데이트 객체를 훅의 큐에 담습니다. 이때 원형 연결 리스트(Circular Linked List) 구조를 사용합니다.
jsx
function enqueueRenderPhaseUpdate<S, A>(
queue: UpdateQueue<S, A>,
update: Update<S, A>,
): void {
didScheduleRenderPhaseUpdateDuringThisPass = didScheduleRenderPhaseUpdate = true;
const pending = queue.pending;
if (pending === null) {
// 첫 업데이트인 경우: 자기 자신을 가리키는 원형 구조 형성
update.next = update;
} else {
// 이미 업데이트가 있는 경우: 기존 리스트의 끝에 삽입
update.next = pending.next;
pending.next = update;
}
queue.pending = update;
}(*원형 연결 리스트 구조를 선택한 이유는 이후 챕터에서 설명합니다.)
2. 불필요한 리렌더링 방지: 즉시 반환 최적화
리액트는 idle 상태에서 업데이트가 발생하고 다음 조건과 같다면 성능 최적화를 수행할 수 있습니다.
최적화 조건
스케줄링된 작업이 없을 때: 현재 컴포넌트의 업데이트로 인해 Work가 스케줄링 되어있지 않은 상태
상태 결과값이 이전과 같을 때: 업데이트 결과 값이 동일한 경우
최적화 결과
이 조건이 충족되면 리액트는 scheduleUpdateOnFiber를 호출하지 않고 즉시 반환하여 컴포넌트 함수를 다시 실행하는 과정 자체가 생략되므로 성능을 보존할 수 있습니다.