지난번 포스트에서 언급했던 외부 주입 역할을 하는(의존성 관리) ReactSharedInternals.js와 shared 패키지에 대해 자세히 알아보려고 합니다.
우선 전체적으로 hook은 이런 흐름으로 개발자에게 전달됩니다.
1. reconciler ->
2. shared/ReactSharedInternal ->
3. react/ReactSharedInternal ->
4. react/ReactCurrentDispatcher ->
5.react/ReactHooks ->
6. react -> 개발자
이때 shared/ReactSharedInternal, react/ReactSharedInternal는 중간다리 역할을 합니다.
core의 ReactSharedInternal.js는 모듈 대기소 라고 할 수 있습니다. 여기 있는 모듈들을 외부에서 주입받기를 대기 하고 있습니다.
shared 패키지는 공용 폴더 역할을 합니다. 그리고 그 중에서도 실제 ReactSharedInternal에 외부 주입을 진행하는 역할은 ReactSharedInternal.js 가 진행합니다.
core(react)의 ReactSharedInternal.js와 이름이 같다...;;
import React from 'react'
const ReactSharedInternals =
React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED // core의 ReactSharedInternals.js
if (!ReactSharedInternals.hasOwnProperty('ReactCurrentDispatcher')) {
ReactSharedInternals.ReactCurrentDispatcher = {
current: null,
}
}
/*...*/
export default ReactSharedInternals
실제 hook을 주입하는 역할은 reconciler/renderWithHooks()가 수행합니다. 이 함수는 Render phase에서 실행됩니다.
우선 전체 코드는 아래와 같습니다.
export function renderWithHooks(
current: Fiber,
workInProgress: Fiber,
Component: any,
props: any,
refOrContext: any,
nextRenderExpirationTime: ExpirationTime
) {
/*...*/
currentlyRenderingFiber = workInProgress // 현재 작업 중인 fiber를 전역으로 잡아둠
nextCurrentHook = current !== null ? current.memoizedState : null
ReactCurrentDispatcher.current =
nextCurrentHook === null ? HooksDispatcherOnMount : HooksDispatcherOnUpdate
let children = Component(props, refOrContext)
/*컴포넌트 재호출 로직..*/
const renderedWork = currentlyRenderingFiber
renderedWork.memoizedState = firstWorkInProgressHook
ReactCurrentDispatcher.current = ContextOnlyDispatcher
currentlyRenderingFiber = null;
/*...*/
}
가장 먼저 컴포넌트를 호출할는 아래쪽 코드를 살펴보면 memoizedState에 무엇인가를 저장합니다. 이때 저장되는 firstWorkInProgressHook는 전역변수에 저장되어 있는 hook 연결리스트의 head입니다..
그리고 이 memoizedState과 current의 존재 유무로 mount 되어야 하는지 update 되어야 하는지 여부를 판단합니다.
위의 코드에 나와있는 실제 훅 구현체 코드는 아래와 같습니다.
// mount
const HooksDispatcherOnMount = {
useState: mountState,
useEffect: mountEffect,
/*...*/
};
// update
const HooksDispatcherOnUpdate: = {
useState: updateState,
useEffect: updateEffect,
/*...*/
};
// invalid hook call
export const ContextOnlyDispatcher: Dispatcher = {
useState: throwInvalidHookError,
useEffect: throwInvalidHookError,
/*...*/
};