TMI) 네이버랩스 인턴을 마치고 10일간 미국에서 쉬다왔다. 요즈음엔 개발 공부하는 것에 재미를 다시 붙이고 있다. 어쩌다보니 리액트 소스코드를 뜯어보는데에 재미를 붙여서, 동시에 이것저것 찾아보다보니 정보의 레이어가 쌓이는 장점도 있지만 그만큼 더 헷깔리는 것들이 생기는 것 같기도 하다. (특히 다양한 자료에서 용어를 혼용하는 경우가 많다..) 정보의 홍수와 AI로 인한 FOMO 속에서 혼란스러운 개발자 생태계 안에서 다음 스텝을 준비하는 입장에서, 리액트 소스코드를 뜯어본 경험은 안티프래질하다고 볼 수 있지 않을까 ..? 라는 생각이다.
Fiber
React는 컴포넌트 하나마다 Fiber 객체를 만든다. state는 React 내부의 Fiber 객체에 저장되는데, state와 관련된 필드만 보면
Fiber {
memoizedState → Hook 링크드 리스트 (useState 값들이 여기 저장됨)
memoizedProps → 가장 최근에 반영된 props
pendingProps → 이번 렌더링에서 새로 받은 props
stateNode → 실제 DOM 노드
}
Fiber는 컴포넌트와 1:1 대응한다.
<Board> → Board Fiber
<Square /> → Square Fiber
<Square /> → Square Fiber
...
Hook Linked List
useState를 여러 개 쓰면 React 내부에서 Hook 객체들이 Linked List로 연결된다.
function Board() {
const [squares] = useState(Array(9).fill(null)); // Hook 1
const [isX] = useState(true); // Hook 2
const [count] = useState(0); // Hook 3
}
Fiber.memoizedState
↓
Hook { memoizedState: [null,...], next → }
Hook { memoizedState: true, next → }
Hook { memoizedState: 0, next → null }
여기서 "Hook 객체"는 우리가 호출하는 useState 함수가 아니라, 그 값을 저장하는 내부 데이터 구조다.
React는 Hook을 이름이 아닌 호출 순서로만 구분한다. 변수명 squares, isX, count는 React 내부에서 보이지 않는다.
조건문 안에 Hook을 쓰면 안 되는 이유
// ❌
function Board() {
const [squares] = useState(...); // Hook 1
if (something) {
const [isX] = useState(...); // 어떨 땐 Hook 2, 어떨 땐 없음
}
const [count] = useState(...); // Hook 2 또는 3으로 매칭됨 → 엉뚱한 값 반환
}
순서가 바뀌면 링크드 리스트 탐색 결과가 달라져서 잘못된 값이 반환된다.
Rules of Hooks
- 최상위에서만 호출 (조건문, 반복문, 중첩 함수 안에 넣지 말 것)
- React 함수 컴포넌트 안에서만 호출
단, 컴포넌트 단위 조건부 렌더링은 문제없다. 컴포넌트가 마운트/언마운트되면 그 Fiber 자체가 생겼다 사라지므로 다른 컴포넌트의 Hook 순서에 영향이 없다.
mountState vs updateState
React는 최초 렌더링인지 재렌더링인지에 따라 다른 함수를 실행한다.
// 최초 렌더링
function mountState(initialState) {
const hook = mountWorkInProgressHook(); // 새 Hook 객체 생성
hook.memoizedState = initialState; // 초기값 저장
return [hook.memoizedState, dispatch];
}
// 재렌더링
function updateState(initialState) {
const hook = updateWorkInProgressHook(); // 기존 Hook 재사용
// initialState는 무시됨
const newState = processUpdateQueue(hook); // 큐 처리 → 최신값
hook.memoizedState = newState;
return [hook.memoizedState, dispatch];
}
dispatcher가 렌더링 단계에 따라 교체된다.
ReactCurrentDispatcher.current =
isMount ? HooksDispatcherOnMount : HooksDispatcherOnUpdate;
업데이트 큐
setSquares(nextSquares)를 호출하면 React는 즉시 값을 바꾸지 않는다. 업데이트를 큐에 쌓고 리렌더링을 예약한다.
function dispatchSetState(fiber, queue, action) {
const update = { action, next: null };
enqueueUpdate(fiber, queue, update); // 큐에 등록
scheduleUpdateOnFiber(fiber); // 리렌더링 예약
}
Hook {
memoizedState: [null, null, ...] ← 아직 이전 값
queue: Update { action: ['X', null, ...] }
}
재렌더링 시 processUpdateQueue가 큐를 처리해서 최신값을 계산하고 memoizedState에 반영한다. 여러 번의 setState를 한 번에 처리하는 배칭(batching)이 이 구조 덕분에 가능하다.
Element vs Fiber
| React Element | Fiber | |
| 정체 | JSX가 변환된 순수 객체 | 작업 단위 + 상태 저장소 |
| 생명주기 | 매 렌더링마다 새로 생성, 버려짐 | 재사용, 오래 유지됨 |
| 역할 | "이런 컴포넌트를 그려줘" 설명서 | 실제 state, props, DOM을 들고 있음 |
재렌더링마다 새 Element 트리가 만들어지면, React는 기존 Fiber 트리와 비교한다. 이게 재조정(Reconciliation) 이다.
같은 자리 + 같은 타입 → Fiber 재사용, memoizedProps 업데이트
같은 자리 + 다른 타입 → 기존 Fiber 버리고 새로 생성
자리가 없어짐 → Fiber 소멸 (언마운트)
'Frontend > 🌐 React' 카테고리의 다른 글
| 🌗 Styled Components로 라이트/다크 모드 전환하기 (0) | 2025.03.26 |
|---|---|
| Styled Components - 애니메이션, 컴포넌트 선택자, 중첩 스타일링 (0) | 2025.03.26 |
| Styled Component에서 컴포넌트 확장하기 (0) | 2025.03.26 |
| React validation 리액트 유효성 검사 Formik VS REACT Hook form (0) | 2023.01.12 |
| react iframe에 의해 클릭 안되는 문제 해결 (0) | 2023.01.06 |
댓글