hook?, useState의 진입점은 어디인가?

KB LEE·2025년 4월 1일
0

React Core

목록 보기
2/2
post-thumbnail

목적

  • React의 hook의 로직을 따라가며 분석
    • useState의 진입점은 어디인가?
    • useState를 통해 상태값을 변경할 때, 내부에선 어떤 작업이 수행되는가?

상세내용

진입점은 어디인가?

// 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에 특정 조건들을 통해 HooksDispatcherOnMountHooksDispatcherOnUpdate로 주입해주고 있었습니다.


정리

React에서 hook(useState)은 외부에서 주입되고 있습니다.

hook을 사용하는 모든 페이지에서 shared/ReactSharedInternals 의 ReactSharedInternals를 통해 사용합니다.
하지만 ReactSharedInternals는 ReactSharedInternalsClient.js 내부의 전역변수를 참조하고 있었습니다.

외부에서 해당 전역변수의 값을 할당하며 shared해주는 구조인지 확인하기 위해서 Repo를 탐색해보았고, ReactFiberHooks.jsrenderWithHooks 함수에서 HooksDispatcherOnMount, HooksDispatcherOnUpdate 를 통해 주입해주고 있었습니다.

다음 시간엔 알아보지 못했던 HooksDispatcherOnMount와 HooksDispatcherOnUpdate의 상세구현을 분석해보겠습니다!

읽어주셔서 감사합니다.

여러분의 소중한 의견을 언제나 환영합니다.😊

profile
한 발 더 나아가자

0개의 댓글