1️⃣ useState가 코드로 정의된 곳은 어디일까? Reconciler 까지 찾아가기!
- react 코어 → ReactHooks.js 모듈 → ReactCurrentDispatcher 모듈 → current:null 인 객체가 끝?
(리액트 코어 패키지는 react 요소에 대한 내용만 포함할 뿐 훅을 구현하고있지 않다.) - Render Phase에서 VDOM으로 리액트 요소가 올라갈때 hook에 대한 정보를 포함하게 된다. (리액트 요소가 Fiber로 확장되기 전에는 필수적인 정보만 포함한다)
- VDOM에 재조정 하기 위한 과정중 하나인 Fiber로 확장하는 작업은 Reconciler가 Render Phase에서 수행하게된다.
따라서 Reconciler가 hook에 대한 정보를 알 수 있을것이라 추측하고, Reconciler가 React 코어 패키지로 어떻게 전달하는지 관심을 가질 수 있다. - 따라서 ReactCurrentDispatcher 를 검색했을때 ReactSharedInternals.js 파일을 찾을 수 있는데, 이는 파일 이름에서 추측할 수 있듯 외부에서 주입받을 수 있는 환경을 제공한다.
- 이때 리액트에서는 shared 라는 패키지를 두어 react 코어 패키지와 외부 패키지의 의존성을 끊음과 동시에 정보를 전달할 수 있게한다 (출입구 역할)
- ReactCurrentDispatcher는 ReactSharedInternals 객체 내부에 포함되어 있다
- 최종적으로 Reconciler에서 shared의 ReactSharedInternals를 이용해 hooks 의 정보를 React 코어 패키지로 전달한다.
→ React 코어(react) 패키지는 위 4번의 ReactSharedInternals를 통해 ReactCurrentDispatcher에 접근한다.
2️⃣ 알겠고, ReactCurrentDispatcher는 뭐가 할당되는 건데? (renderWithHooks)
ReactCurrentDispatcher.current 에 할당되는 것을 찾기 위해선 Reconciler 패키지의 renderWithHooks() 의 구현을 보면된다.
currentlyRenderingFiber = workInProgress;
- 리액트 내부에서 훅을 호출할 때 어떤 컴포넌트를 렌더링중인지 전역적으로 알려주는 역할
- 왜 필요할까?
→훅(useState, useEffect, useReducer 등)은 반드시 함수형 컴포넌트 내에서만 호출되어야 하며, 호출 순서도 보장되어야 한다. 따라서 이를 보장하기 위해 리액트는 훅이 호출될 때마다 그 훅이 어느 컴포넌트에서 호출되었는지 알아야 한다.
nextCurrentHook = current !== null ? current.memoizedState : null;
- current 란?
이미 commit 되어 DOM에 반영된 Fiber 이다. (이전 포스팅 [React 까보기] 2. VDOM 과 React lifecycle 참고)
업데이트 중일 경우 WorkInProgress.alternate가 이 current이다.
즉, current !== null이면 update, null이면 mount 이다.
current === null → DOM에 반영된 정보가 없다 → 마운트를 해야한다
current !== null → 이미 DOM에 반영된 정보가 있다 → 마운트가 끝났다(업데이트 상태) - nextCurrentHook 이라는 변수명에서 미루어보아 current.memoizedState 에는 훅이 들어있을것이다! 라고 추측하고 넘어가기 (7번 과정에서 알 수 있음)
- current 란?
ReactCurrentDispatcher.current = nextCurrentHook === null ? mount(HooksDispatcherOnMount) : update(HooksDispatcherOnUpdate);
// HooksDispatcherOnMount, HooksDispatcherOnUpdate 는
// 아래와 같이 같은 이름의 서로 다른 훅 구현을 포함하고있는 객체이다.
const HooksDispatcherOnMount:Dispatcher = {
readContext,
useCallback: mountCallback,
useEffect: mountEffect,
useState: mountState,
// ... other hooks
}
const HooksDispatcherOnUpdate:Dispatcher = {
readContext,
useCallback: updateCallback,
useEffect: updateEffect,
useState: updateState,
// ... other hooks
}
let children = Component(props, refOrContext);
- 렌더링의 과정(컴포넌트 호출 → 결과를 얻고 → VDOM에 반영) 중 컴포넌트의 호출을 이부분에서 담당한다
didScheduleRenderPhaseUpdate 플래그에 따른 if문 로직 처리
- 상태를 업데이트 했을때 변하는 일련의 과정을 Update라고 일단은 알아두자 (리액트 Scheduler에서 자세히 알아볼 예정)
- 현재는 mount 기준으로 실행 흐름을 보고있기때문에 일단은 건너뛰기
ReactCurrentDispatcher.current = ContextOnlyDispatcher;
- 위의 3번에서 할당했는데 한번 더 할당한다???
- 컴포넌트의 호출(마운트 또는 업데이트, 일련의 과정) 후에 할당 구문이 실행된다.
- 이때 훅을 호출하면 안되는 상황에서 훅을 덮어써 에러를 발생시키기 위해 ContextOnlyDispatcher를 할당하는 것!
// ContextOnlyDispatcher 내부에는 각 훅에 대해서 throwInvalidHookError 가 들어가있음
const ContextOnlyDispatcher:Dispatcher = {
readContext,
useCallback: throwInvalidHookError,
useEffect: throwInvalidHookError,
useState: throwInvalidHookError,
// ... other hooks
}
const renderedWork: Fiber = (currentlyRenderingFiber: any); renderWork.memoizedState = firstWorkInProgressHook;
- 이를 통해 fiber의 memoizedState에는 hook이 담긴다는 것을 도출 할 수 있음!
- firstWorkInProgressHook를 더 자세히 알아보면, hook 이라는 객체를 할당하고 있음
- firstWorkInProgressHook에 hook 객체를 할당하는 함수(mountWorkInProgress)는 언제 호출되느냐? → mountState(useState) 에서 호출됨!
- 따라서 이 과정은 Fiber에 훅 정보를 연결해주는 로직이라고 볼 수 있다
( = Reconciler 의 renderWithHooks 는 Fiber에 훅 정보를 연결해줌을 알 수 있다)
currentHook = null; nextCurrentHook = null; firstWorkInProgressHook = null; workInProgressHook = null; nextWorkInProgressHook = null;
- 이번 과정을 통해서 "전역변수"를 전부 초기화를 한다! → 이것이 의미하는 것은 파일안에서만 유효한 변수가 됐음
- 컴포넌트를 작업할때 사용하고 다음 컴포넌트가 작업될때는 전역변수들을 초기화 했기때문에 이전 변수들의 값을 사용하지 않음
return children;
- 4번 과정에서 담아두었던 컴포넌트를 리턴하고 함수가 끝나게 됨
결국 현재 상태가 MountPhase 인지 UpdatePhase 인지에 따라 훅 정보를 넣어준다.
해당 훅 정보를 넣어주는 작업이 바깥에서 훅을 쓸 수 있게 하는 것이다.
📚 레퍼런스
'FE > React' 카테고리의 다른 글
[React 까보기] 5. setState (feat. dispatchAction) (0) | 2025.06.11 |
---|---|
[React 까보기] 4. useState 의 구현체 (0) | 2025.06.11 |
[React 까보기] 2. VDOM 과 React lifecycle (0) | 2025.04.14 |
[React 까보기] 1. 리액트 패키지의 구성 요소들 & 용어 정의 (0) | 2025.04.14 |