Open9
react-reconcilerからみたreact-three-fiberの分析

レポジトリ
これを分析したい
モノレポ化しているが、実態はpackages/fiber
に存在する。

ディレクトリ構造
reactとreact-native両方で利用できるように、イベントを抽象化している。また環境差分はCanvasで吸収している。
src
- core/*
- native/*
- web/*
- index.tsx
- native.tsx
- three-types.ts
フォルダ/ファイル名 | 説明 |
---|---|
core | 本体 |
native | react-native用のCanvasとeventハンドラ |
web | react-dom用のCanvasとeventハンドラ |
index.tsx | react-dom用のエントリポイント |
native.tsx | react-natvei用のエントリポイント |
three-types.ts | 共通の型 |

Canvasコンポーネントでやっていること
rendererの初期化と初回描画の実行をやっている
-
extend
によるTHREEクラスの登録(reconcilerにstateとして保持されレンダリング時に利用される) - 独自の
Bridge
コンポーネント機構により、異なるrenderer間でもContextProviderから値を受け取れるようにしている - webの場合: canvasを
createRoot
してrendererを初期化、configure
で設定したのちに、render
を実行 - nativeの場合:
GLView
から疑似的なcanvasを作成、createRoot
してrendererを初期化、configure
で設定したのちに、render
を実行

coreディレクトリの分解
- events.ts
- hooks.ts
- index.ts
- loop.ts
- reconciler.tsx
- renderer.tsx
- store.ts
- utils.tsx
フォルダ/ファイル名 | 説明 |
---|---|
events.ts | |
hooks.tsx | |
index.tsx | 再エクスポート用のファイル |
loop.ts | |
reconciler.tsx | |
rerenderer.tsx | |
store.ts | |
utils.tsx |

renderer.tsx
のやっていること
- createRoot
- storeの初期化
- reconciler.createContainerにstoreをcontainerとして渡す
- つまり、reconcilerの監視対象はstoreになる(面白い!)
- ReconcilerRootインスタンスを返す
- configureメソッド: storeに設定を渡す
- gl(レンダラ)
- raycaster
- camera
- scene
- events
- dpr
- frameloop
- onPointerMissed
- performance
- xr
- shadowmap
- renderメソッド:
- configureを実行してなかったら実行
- reconciler.updateContainerの実行
- Providerにstoreの値を渡す
- childrenにrenderの引数ReactNodeを渡す
- unmountメソッド
-
unmountComponentAtNode
の実行
-
- configureメソッド: storeに設定を渡す

store
の内容
- zustand を使って React コンテキスト経由で状態をグローバル共有。
- THREE.WebGLRenderer, THREE.Scene, Camera, Raycaster などの主要リソースを一元管理。
- 描画ループ、サブスクリプション管理、インタラクション(マウス/タッチイベント)管理を担当。
- サブスクライブによって camera, size, dpr の変更に応じて gl や viewport を更新。
- stateが変更されたら
invalidate
により再描画要求
プロパティ | 説明 |
---|---|
gl |
Three.js の WebGLRenderer
|
camera |
デフォルトカメラ(Perspective/Orthographic) |
scene |
Three.js のルートシーン |
raycaster |
マウスイベント用のレイキャストユーティリティ |
events |
イベント管理用オブジェクト(EventManager) |
frameloop |
'always' , 'demand' , 'never' :再描画ルールを制御 |
performance |
レンダリングパフォーマンス制御 |
size , viewport
|
Canvas サイズとThree.js空間におけるビューポートサイズ |
internal |
インタラクション、subscriber、初期クリック/ヒットなどの内部状態 |
invalidate() |
再描画を要求(demandモード用) |
advance() |
レンダーループ1ステップの実行(主に外部制御用) |
- frameloop = 'always':毎フレーム描画するのでinvalidateによる再描画要求は無視。
- frameloop = 'demand':必要なときだけ描画する → 状態が変わったときだけ invalidate したい。
- frameloop = 'never':ユーザーが advance() を呼ばない限り描画されない。
InternalState – サブスクリプションとイベント処理
useFrame
hookによってコールバック関数を登録する。ループ処理のupdate
時に呼ばれる。
- subscribers: 描画に関心のある関数のリスト。priorityで順序付け。
- subscribe(): コンポーネントがレンダーループに参加するときに登録。
- hovered, interaction, capturedMap: マウスやタッチのイベント管理。
- lastEvent: 最新のイベントを保持。

ループ処理について
- invalidateが実行されると、
- 現在レンダリング中なら、内部フレームを2にする
- レンダリング中でないなら、内部フレームを1にする
- running =falseならloop実行
- loopが実行されると
-
requestAnimationFrame(loop)
で次のループを予約 - repeatを0にセット
- frameloop='always'ならrepeat = 1、flrameloop='demand'であれば、内部フレームが2であればrepeat=1、内部フレームが1であればrepeat=0
- repeat=0であれば、running=falseにして、
cancelAnimationFrame(frame)
-

Reconcilerについて

まとめ
初期化時
- 設定をいろいろしてrendererの初期化
- zustandのstateを初期化
- rendererのrender実行
propsの更新時
- React 側で zustand の state が更新される。
- store.subscribe(() => invalidate(state)) が発火。
- frameloopが
never
でなければ、invalidate() によって描画ループを開始。 - loop() が update() を呼び、gl.render() を行う。frameloop='always'であればループを実行し続ける。frameloop='demand'であれば変化があったフレームの数だけループを実行