useState를 호출한다는 것은 mountState 함수를 호출하는 것과 같다는 것을 이전 포스팅에서 알아보았다.
따라서 우리가 잘 알고있는 setState를 통해 상태를 업데이트 하는 과정은 dispatch 함수를 분석한다면 알 수 있을 것 이다.
위에서 말한 dispatch 함수에는 dispatchAction.bind() 함수의 값이 할당 되어 있으므로 dispatchAction을 분석해보겠다.
1️⃣ dispatchAction 함수
function dispatchAction<S, A>(
fiber: Fiber,
queue: UpdateQueue<S, A>,
action: A,
) {
// 3. update 소비중 다시 update 발생
// renderWithHooks 내부에서 1씩 증가시켰던 numberOfRerenders의 값을 이용하여 에러를 내보냄
invariant(
numberOfReRenders < RE_RENDER_LIMIT,
'Too many re-renders. React limits the number of renders to prevent ' +
'an infinite loop.',
);
// idle 상태(일을하지않는, 업데이트가 없는 상태)와 Render Phase를 구분하는 조건문
// 아래 두가지 Fiber 노드를 전부 확인해야 실제로 Render Phase인지 확인 가능
// - currentlyRenderingFiber => 업데이트가 일어나고 있는 Fiber 노드(current Fiber 노드)
// - alternate => VDOM 구조에서 current fiber의 참조복사가 되어있는 Fiber 노드(workInProgress Fiber 노드)
const alternate = fiber.alternate;
if (
fiber === currentlyRenderingFiber ||
(alternate !== null && alternate === currentlyRenderingFiber)
) {
// render phase 업데이트가 실행된다 라는 의미
didScheduleRenderPhaseUpdate = true;
// 0. update 객체 생성
const update: Update<S, A> = {
expirationTime: renderExpirationTime,
suspenseConfig: null,
action,
eagerReducer: null,
eagerState: null,
next: null,
};
// 1. update의 저장
// Render Phase에서 발생한 업데이트를 저장
if (renderPhaseUpdates === null) {
renderPhaseUpdates = new Map();
}
const firstRenderPhaseUpdate = renderPhaseUpdates.get(queue);
if (firstRenderPhaseUpdate === undefined) {
renderPhaseUpdates.set(queue, update);
}
else {
let lastRenderPhaseUpdate = firstRenderPhaseUpdate;
while (lastRenderPhaseUpdate.next !== null) {
lastRenderPhaseUpdate = lastRenderPhaseUpdate.next;
}
lastRenderPhaseUpdate.next = update;
}
// 2. update의 소비 => renderWithHooks에서 didScheduleRenderPhaseUpdate의 값을 이용
} else {
const currentTime = requestCurrentTimeForUpdate();
const suspenseConfig = requestCurrentSuspenseConfig();
const expirationTime = computeExpirationForFiber(
currentTime,
fiber,
suspenseConfig,
);
// 1. update 객체 생성
const update: Update<S, A> = {
expirationTime, // work가 진행될때 값을 할당함
suspenseConfig,
action, // setState의 인자 값
eagerReducer: null, // 불필요 렌더링 최적화 단계와 관련이 있음(1)
eagerState: null, // 불필요 렌더링 최적화 단계와 관련이 있음(2)
next: null, // update 객체도 linked list 형태로 저장됨
};
// 2. update 객체를 큐에 저장
// 아래 로직은 원형 큐를 구현하여 새로운 update 객체를 넣는 로직
// 원형 큐로 구현한 이유는 최적화와 관련이 있음!
const last = queue.last;
// last === null 이면 큐가 비어있다는 뜻(최초)
if (last === null) {
update.next = update; // 자기 자신을 참조시킴(초기 원형 큐 준비단계)
}
// 큐에 update 객체가 존재할때(최초가 아닐때)
else {
const first = last.next; // 첫번째 노드(update객체)
if (first !== null) {
update.next = first; // 새로운 update 객체에 next로 큐의 첫번째 노드를 연결
}
last.next = update; // 큐의 마지막을 새로운 update 객체로 변경
}
// update 객체를 큐에 연결
queue.last = update;
// 3. 불필요 렌더링 발생하지 않도록 최적화
// 아래 조건이 만족하지 않는 경우 return (함수 중지)
// - WORK 스케줄링 X
// - action의 결과값 === 현재 상태값
if (
fiber.expirationTime === NoWork &&
(alternate === null || alternate.expirationTime === NoWork)
) {
const lastRenderedReducer = queue.lastRenderedReducer; // basicStateReducer
if (lastRenderedReducer !== null) {
let prevDispatcher;
try {
const currentState: S = (queue.lastRenderedState: any); // 마지막으로 렌더된 값
const eagerState = lastRenderedReducer(currentState, action); // 바뀌어야 할 state 값
update.eagerReducer = lastRenderedReducer;
update.eagerState = eagerState;
// 상태가 같다면 함수 종료
if (is(eagerState, currentState)) {
return;
}
}
}
}
// 4. WORK를 스케줄링
// Render Phase 진입점
scheduleWork(fiber, expirationTime);
}
}
- idle 상태가 아닐때의 setState(Render Phase 일때)
- update의 저장
- update의 소비 => renderWithHooks 함수에서 update를 소비함
(renderWithHooks 내의 didScheduleRenderPhaseUpdate 조건문 부분 확인) - update 소비 중에 다시 update의 발생
(renderWithHooks 내의 didScheduleRenderPhaseUpdate 조건문 부분 확인)
- idle 상태일때의 setState
- update 객체 생성
- update를 queue 객체에 저장
- 불필요한 렌더링 발생하지 않도록 최적화
- update를 적용하기 위해 WORK를 Scheduler에 예약(scheduling)
- WORK: Reconciler가 컴포넌트의 변경을 DOM에 적용하기 위해 수행하는 일
📚 레퍼런스
- [React 까보기 시리즈] setState 를 여러번 호출하면 실제로 상태를 여러번 업데이트할까?(상태를 업데이트 하기 위해 queue 에 저장하고 소비하는 과정)
- [React 까보기 시리즈] setState 를 호출하는 2가지 상황, idle 과 render phase 를 구분하는 조건문?!
- [React 까보기 시리즈]setState 를 호출했을 때, render phase 에서 벌어지는 일?!(render phase 에서 update 를 저장하고 소비하는 과정)
- https://github.com/facebook/react/blob/v16.12.0/packages/react-reconciler/src/ReactFiberHooks.js#L1221
'FE > React' 카테고리의 다른 글
[React 까보기] 4. useState 의 구현체 (0) | 2025.06.11 |
---|---|
[React 까보기] 3. Hook 구현체 찾아가기 (0) | 2025.04.26 |
[React 까보기] 2. VDOM 과 React lifecycle (0) | 2025.04.14 |
[React 까보기] 1. 리액트 패키지의 구성 요소들 & 용어 정의 (0) | 2025.04.14 |