useState
의 setState
호출이 단순히 값을 변경하는 대신 "업데이트 객체"를 생성하고 이를 큐에서 관리합니다.
- React는 왜 단순히 값을 변경하는 대신, 업데이트를 객체로 관리하는가?
- React는 이 객체들을 어떻게 처리하여 최종 상태를 결정하는가?
이 글에서는 React의 내부 구현을 살펴보며 이 의문들에 대한 답을 찾아보겠습니다.
React의 상태 업데이트는 단순한 값 대입이 아닌, 업데이트에 대한 모든 정보를 담은 객체로 만들어집니다.
React의 useState의 실제 구현인 dispatchSetStateInternal
을 살펴봐 상태 업데이트가 어떻게 객체로 변환되는지 실제 코드를 통해 살펴보겠습니다.
function dispatchSetStateInternal<S, A>(
fiber: Fiber,
queue: UpdateQueue<S, A>,
action: A,
lane: Lane,
): boolean {
const update: Update<S, A> = {
lane,
revertLane: NoLane,
action,
hasEagerState: false,
eagerState: null,
next: null,
};
// ...
}
이러한 객체 기반 접근의 주요 이점들을 살펴보겠습니다:
-
상태 업데이트의 추적
const update = { action: newState, // 의도된 변경사항 next: null, // 다음 업데이트와의 연결 };
- 모든 상태 변경을 추적할 수 있음
- 필요한 경우 변경을 취소하거나 재시도 가능
-
우선순위 기반 처리
const update = { lane: SyncLane, // 우선순위 정보 revertLane: NoLane // 복구 정보 };
- 중요한 업데이트를 우선적으로 처리
- 덜 중요한 업데이트는 나중으로 지연
-
최적화와 일관성
const update = { hasEagerState: false, // 즉시 처리 가능 여부 eagerState: null // 미리 계산된 상태 };
- 가능한 경우 상태를 미리 계산
- 불필요한 재렌더링 방지
이러한 객체 기반 관리는 다음 섹션에서 볼 상태 업데이트 처리의 토대가 됩니다.
상태 업데이트는 단순히 발생하는 대로 처리되지 않고, 큐를 통해 체계적으로 관리됩니다.
function mountState<S>(initialState) {
const hook = mountStateImpl(initialState);
const queue = hook.queue;
const dispatch = dispatchSetState.bind(
null,
currentlyRenderingFiber,
queue
);
queue.dispatch = dispatch;
return [hook.memoizedState, dispatch];
}
- 각 상태마다 독립적인 업데이트 큐 유지
- 업데이트의 순서와 우선순위 보장
- 일괄 처리(batching)를 통한 성능 최적화
React는 성능 최적화를 위해 가능한 경우 업데이트를 즉시 처리하려 시도합니다.
if (fiber.lanes === NoLanes &&
(alternate === null || alternate.lanes === NoLanes)) {
// 현재 진행 중인 다른 업데이트가 없다면
const lastRenderedReducer = queue.lastRenderedReducer;
const currentState = queue.lastRenderedState;
const eagerState = lastRenderedReducer(currentState, action);
update.hasEagerState = true;
update.eagerState = eagerState;
if (is(eagerState, currentState)) {
// 상태가 실제로 변경되지 않았다면 업데이트 취소
return false;
}
}
- 다른 진행 중인 업데이트 확인
- 즉시 상태 계산 가능 여부 판단
- 불필요한 업데이트 조기 종료
업데이트의 종류에 따라 서로 다른 처리 전략을 사용합니다.
// 1. 직접 값 설정 - 즉시 계산 가능
setState(5);
// 2. 함수형 업데이트 - 이전 상태 필요
setState(prev => prev + 1);
- 직접 값 설정은 가능한 경우 즉시 처리하고
- 함수형 업데이트는 이전 상태와의 관계를 고려하여 큐에서 순차적으로 처리합니다