// PATH: react/packages/react/index.js
epport {
...,
useState,
} from './src/ReactClient';
// PATH: react/packages/react/src/ReactClient.js
import {
...,
useState,
...
} from './ReactHooks';
// PATH: react/packages/react/src/ReactHooks.js
export function useState<S>(
initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
const dispatcher = resolveDispatcher();
return dispatcher.useState(initialState);
}
useState는 resolveDispatcher
내부에서 가져오고 있는 것을 확인할 수 있습니다.
resolveDispatcher
를 따라가보겠습니다.
// PATH: react/packages/react/src/ReactHooks.js
import ReactSharedInternals from 'shared/ReactSharedInternals';
function resolveDispatcher() {
const dispatcher = ReactSharedInternals.H;
...
// Will result in a null access error if accessed outside render phase. We
// intentionally don't throw our own error because this is in a hot path.
// Also helps ensure this is inlined.
return ((dispatcher: any): Dispatcher);
}
구현체는 shared/ReactSharedInternals
에 있다는 걸 확인할 수 있습니다.
// PATH: react/packages/shared/ReactSharedInternals.js
import * as React from 'react';
const ReactSharedInternals =
**React.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE**;
export default ReactSharedInternals;
내부 구현체를 확인하기위해 ReactSharedInternals.js
를 따라왔지만 예상과는 반대로 상수값을 내려보내고 있었습니다.
__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE
는 무엇일까라는 의문이 들었고, Repo내부에서 더 상세하게 알아보았습니다.
// PATH: react/packages/react/index.js
export {
__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE,
...,
useState,
...
} from './src/ReactClient';
하지만 해당 상수는 아이러니하게도 다시 ReactClient내부에서 찾아볼 수 있었습니다.
// PATH: react/packages/react/src/ReactClient.js
...
import ReactSharedInternals from './ReactSharedInternalsClient';
export {
...,
ReactSharedInternals as __CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE,
...
};
위 코드를 보면 ReactSharedInternals는 ReactSharedInternalsClient
에 있음을 알 수 있습니다.
// PATH: react/packages/react/src/ReactSharedInternalsClient.js
export type SharedStateClient = {
H: null | Dispatcher, // ReactCurrentDispatcher for Hooks
A: null | AsyncDispatcher, // ReactCurrentCache for Cache
...
};
const ReactSharedInternals: SharedStateClient = ({
H: null,
A: null,
...
}: any);
export default ReactSharedInternals;
하지만 정말 끝이라고 생각해서 따라온 곳엔 ReactSharedInternals
라는 객체만 존재했습니다.
그렇다면 hook을 제공해주는 resolveDispatcher = ReactSharedInternals.H
엔 어떤 값이 들어가게 되는 걸까요?
혹시 외부에서 주입해주고 있을까요?
확인을 위해 ReactSharedInternals
에 직접 주입해주고 있는 코드가 있는지 찾아보았습니다.
// PATH: react/packages/react-reconciler/src/ReactFiberHooks.js
import ReactSharedInternals from 'shared/ReactSharedInternals';
export function renderWithHooks<Props, SecondArg>(
current: Fiber | null,
workInProgress: Fiber,
Component: (p: Props, arg: SecondArg) => any,
props: Props,
secondArg: SecondArg,
nextRenderLanes: Lanes,
): any {
// The following should have already been reset
// currentHook = null;
// workInProgressHook = null;
// didScheduleRenderPhaseUpdate = false;
// localIdCounter = 0;
// thenableIndexCounter = 0;
// thenableState = null;
// TODO Warn if no hooks are used at all during mount, then some are used during update.
// Currently we will identify the update render as a mount because memoizedState === null.
// This is tricky because it's valid for certain types of components (e.g. React.lazy)
// Using memoizedState to differentiate between mount/update only works if at least one stateful hook is used.
// Non-stateful hooks (e.g. context) don't get added to memoizedState,
// so memoizedState would be null during updates and mounts.
if (__DEV__) {
...
} else {
ReactSharedInternals.H =
current === null || current.memoizedState === null
? HooksDispatcherOnMount
: HooksDispatcherOnUpdate;
}
...
return children;
}
ReactSharedInternals
를 주입하는 부분을 찾아오다보니, react-reconciler/src/ReactFiberHooks.js 라는 곳까지 오게 되었습니다.
renderWithHooks
에서 ReactSharedInternals.H에 특정 조건들을 통해 HooksDispatcherOnMount와 HooksDispatcherOnUpdate로 주입해주고 있었습니다.
React에서 hook(useState)은 외부에서 주입되고 있습니다.
hook을 사용하는 모든 페이지에서 shared/ReactSharedInternals
의 ReactSharedInternals를 통해 사용합니다.
하지만 ReactSharedInternals는 ReactSharedInternalsClient.js
내부의 전역변수를 참조하고 있었습니다.
외부에서 해당 전역변수의 값을 할당하며 shared해주는 구조인지 확인하기 위해서 Repo를 탐색해보았고, ReactFiberHooks.js
의 renderWithHooks
함수에서 HooksDispatcherOnMount, HooksDispatcherOnUpdate 를 통해 주입해주고 있었습니다.
다음 시간엔 알아보지 못했던 HooksDispatcherOnMount와 HooksDispatcherOnUpdate의 상세구현을 분석해보겠습니다!
읽어주셔서 감사합니다.
여러분의 소중한 의견을 언제나 환영합니다.😊