Upgrade to Pro — share decks privately, control downloads, hide ads and more …

React本体のコードリーディング

Avatar for high-g high-g
July 29, 2026

 React本体のコードリーディング

2026/07/29(水) Kansai Frontend Meetup #2 フロントエンド何でもLT大会
https://fec-kansai.connpass.com/event/396639/

Avatar for high-g

high-g

July 29, 2026

More Decks by high-g

Other Decks in Programming

Transcript

  1. この発表で目指したいゴール 話すこと 大まかな react/react リポジトリの構成について react本体を起点にしたuseStateの定義 話さないこと react-reconciler の網羅的な内容 react-dom

    や Scheduler の深い内容 深い実装内容諸々 Fiber 目指したいゴール 本体のコードリーディング入門的な内容を共有し、 聞いている人にOSSの中身を知りたいというモチベーションになってもらう React
  2. react/react 開発ツールチェーン トランスパイラ Babel バンドラー Rollup 型システム Flow パッケージマネージャー yarn

    1 社製、歴史的経緯にTypeScriptではなくFlowが採用 Meta 系 古いものが現役で使われている。これもMeta社製。 (察し)
  3. react/react ディレクトリ構造 react/react/ ├── compiler/ ├── packages/ compiler/* React Compiler

    のコード。今回は割愛 │ ├── react/ │ ├── react-dom/ │ ├── react-reconciler/ packages/* │ ├── react-devtools/ react react-dom react-devtools react-native │ ├── react-native-renderer/ │ ├── shared/ │ └── … ├── scripts/ └── package.json 、 、 の関連パッケージ react/react 、 用レンダラーなど多数 = 多数のパッケージを格納したモノレポ
  4. packages/react の構成 packages/react/ ├── npm/ ├── src/ npm/ で手元に届くもの。厳密には npm/

    + package.json + + ビルド成果物 npm install react README.md ├── index.js ├── index.stable.js ├── index.experimental.js ├── index.development.js src/ バンドルされる前のソースコード。RSC・コンパイラ関連のコードもある(今回は 割愛) ├── package.json └── README.md index.(*.)js ほか直下のファイル index.js / index.stable.js / index.experimental.js / index.development.js / package.json — エントリーポイント群(次のスライドで詳しく)
  5. packages/react 直下に存在するファイル で利用されるもの index.stable.js react@latest index.experimental.js react@experimental index.(*.)development.js NODE_ENV=development index.js

    型定義・テスト実行系など、ビルドパイプライン外の内部開発用の入口 で利用されるもの の開発環境で動くもの 役割の違いを除けば、 各ファイルの差分は package/src/ReactClient.js からの re-export 内容のみ
  6. packages/react/src/ReactClient.js import ReactVersion from 'shared/ReactVersion'; export { import { Children,

    REACT_FRAGMENT_TYPE, REACT_STRICT_MODE_TYPE, Component, PureComponent, REACT_SUSPENSE_TYPE, /* … */ createContext, } from 'shared/ReactSymbols'; import {Component, PureComponent} from './ReactBaseClasses'; lazy, memo, import {createElement, cloneElement, isValidElement} useState, from './jsx/ReactJSXElement'; useEffect, import {createContext} from './ReactContext'; import {lazy} from './ReactLazy'; useMemo, useRef, import {memo} from './ReactMemo'; REACT_FRAGMENT_TYPE as Fragment, import { useState, useEffect, useMemo, useRef, /* … */ REACT_SUSPENSE_TYPE as Suspense, createElement, } from './ReactHooks'; ReactVersion as version, import ReactSharedInternals from './ReactSharedInternalsClient'; startTransition, import {startTransition} from './ReactStartTransition'; import {act} from './ReactAct'; // … }; // … にあるファイルを re-export しているだけ — ロジックは1行もありません packages/react/src/
  7. ReactClient.js shared/ReactSymbol に import されているファイル群 export const REACT_XXX: symbol =

    Symbol.for('react.xxx'); (例)Fragment, Suspense, Memo など ReactBaseClasses クラスコンポーネントの後方互換 jsx/ReactJSXElement JSX ReactChildren 不定形な children のツールセット ReactContext / Lazy / Memo としてすべての名称を定義 → React エレメント生成 それぞれ Context・遅延読み込み・メモ化コンポーネントを表す特殊オブジェクトを生成する 更新を低優先・中断可能に ReactStartTransition state ReactAct テスト用 API(RTL 向け) ReactHooks Hooks etc. ReactCacheClient ReactOwnerStack の呼び出し窓口 → 後述 、 など他にも多数
  8. ReactClient.js ReactClient.js react がやっていること は実装を持たず、src/ 配下の各ファイルを集約して re-export しているだけ パッケージが実際に担っているのは次の2点のみ API

    の入口 などの API を公開するが、実処理は後段 のメイン計算処理(差分計算・描画)に委譲する useState マーカー付きオブジェクトの生成 { $$typeof: Symbol(...), ... } 生成する というオブジェクトを JSX → Symbol(react.transitional.element) memo → Symbol(react.memo) forwardRef → Symbol(react.forward_ref) lazy → Symbol(react.lazy) createContext → Symbol(react.context) メインロジックは持たず、 後段の計算処理へ渡す要素オブジェクトを生成するだけ
  9. packages/react/src/ReactHooks.js を見てみる import ReactSharedInternals from 'shared/ReactSharedInternals'; export function useState(initialState) {

    const dispatcher = resolveDispatcher(); return dispatcher.useState(initialState); } // …(中略)… function resolveDispatcher() { const dispatcher = ReactSharedInternals.H; return dispatcher; } 全 Hooks が dispatcher.useXxx へ委譲する宣言のみ。dispatcher の中は ReactSharedInternals.H useState の実装はどこ?
  10. ReactSharedInternals.H 検索 について ReactSharedInternals.H = ↓ ヒット packages/react-reconciler/src/ReactFiberHooks.js のメイン実装は別パッケージにある構造上、コードジャンプでは (

    )の実体にたどり着けません。 一旦、なにも考えずに、 を見ていく react ReactSharedInternals.H dispatcher react-reconciler
  11. ・ ・ Reconciler Fiber Scheduler Reconciler Fiber Scheduler について 要素オブジェクトを受け取り、前回との差分を計算して「どこを更新すべきか」を決めるエンジ

    ン。DOM は直接触らず、実際の反映はレンダラー(react-dom など)が行う が使う作業単位のデータ構造。コンポーネント1つ分の状態や進捗を持ち、リンク構造 なので処理を途中で中断・再開できる Reconciler その計算を「いつ・どの優先度で実行するか」を管理する係。ユーザー入力を優先し、重い計算は 細切れに実行して画面の固まりを防ぐ は、Fiber ツリーを使って差分計算を行い、Scheduler の優先度制御に 従って「どこを更新すべきか」を決める React の心臓部パッケージ react-reconciler
  12. Fiber ツリーは誰が作るのか ̶ Reconciler がツリーを組み⽴てる過程 = 差分計算 ③ Scheduler ①

    react パッケージ 「いつ・どの優先度で」を管理 要素オブジェクトを⽣成するだけ タスク単位で実⾏を制御し、⼀定時間で中断 ※ ツリー構築には関与しない { $$typeof: Symbol(...), type: 'div', props: {...} } Fiber 1つ処理ごとに 実⾏開始の指⽰ shouldYield?(中断確認) ② Reconciler(Render フェーズ) 要素オブジェクトを読みながら、Fiber を1つずつ⽣成・再利⽤してツリーを組み⽴てる current ツリー workInProgress ツリー (現在の画⾯に対応) (組み⽴て中の次の画⾯) 設計図として渡す 新しい要素と⽐較 = 差分計算 再利⽤ 新規作成 作り直し ポイント: workInProgress ツリーを組み⽴てる過程そのものが差分計算 まとめ: 要素オブジェクト(react が⽣成)を設計図として、Reconciler が Fiber ツリーを構築・更新する。Scheduler はそのタイミングを制御するだけ。
  13. packages/react-reconciler/src/ReactFiberHooks.js 関数(503行目)に記載 — useStateが動くための下準備をしてから、コンポーネントを実行する関数 の入口 renderWithHooks // Dispatcher を切り替え ReactSharedInternals.H

    = current === null || current.memoizedState === null ? HooksDispatcherOnMount // 初回 mount 用 : HooksDispatcherOnUpdate; // 更新 update 用 ( ) に、初回か更新かを比較して格納する対象 ReactSharedInternals.H dispatcher が切り替わっている
  14. HooksDispatcherOnMount の実体 const HooksDispatcherOnMount: Dispatcher = { useCallback: mountCallback, useEffect:

    mountEffect, useMemo: mountMemo, useRef: mountRef, useState: mountState, // …(中略)… }; useState に紐づいているのは mountState つまり、dispatcher == ReactSharedInternals.H == HooksDispatcherOnMount になり、 dispatcher.useState == mountState になる!!
  15. mountState の実装はこんな感じ function mountState(initialState) { const hook = mountStateImpl(initialState); //

    └ hook.memoizedState = 初期値、queue を作成 を保存する箱(hook)を作っ て、 の連結リストに繋ぐ 1. state fiber const queue = hook.queue; const dispatch = dispatchSetState.bind( null, currentlyRenderingFiber, // この Fiber の queue, // この queue に積む その箱に更新を届けるための宛先付 き関数(setCount)を作る 2. ); queue.dispatch = dispatch; return [hook.memoizedState, dispatch]; } 注目してほしいのは、戻り値の形! ※ちょっと細かく解説するじかんがねえ・・・ 3. 現在値と関数を、配列で返す
  16. useState 実行フロー function Counter() { const [count, setCount] = useState(0);

    return ( <button sc-camel-on-click={() => setCount(count + 1)}> {count} </button> ); } useState(0) ↓ resolveDispatcher() ↓ ReactSharedInternals.H // dispatcher の実体はただのオブジェクト const HooksDispatcherOnMount = { useState: mountState, useEffect: mountEffect, useMemo: mountMemo, useRef: mountRef, // …全 Hooks }; ↓ 初回レンダリング HooksDispatcherOnMount ↓ dispatcher.useState(0) mountState(0) react react shared react-reconciler react-reconciler
  17. まとめ は React 関連パッケージを内包するモノレポ 01 react/react 02 packages/react 03 処理の根幹は

    packages/react-reconciler(useState の実体もここに) 04 全体は膨大でもAIを使えばガンガン読んでいける!! は、API の入口&マーカー付きオブジェクトの生成がメイン