React Three Fiber
Three.js のシーングラフを React のコンポーネントツリーとして書けるレンダラ。
DOM の代わりに THREE.Mesh や THREE.Light を React reconciler が組み立てる。
略称 R3F。Poimandres チームによる。
これは何か
素の Three.js は命令的に書く: new Mesh() を作って scene.add()、
update ループで位置を更新、終わるときに dispose。R3F はこれを JSX で宣言するだけにする。
const geom = new THREE.BoxGeometry()
const mat = new THREE.MeshStandardMaterial({ color: "orange" })
const mesh = new THREE.Mesh(geom, mat)
scene.add(mesh)
renderer.setAnimationLoop(() => {
mesh.rotation.y += 0.01
renderer.render(scene, camera)
})
function Box() {
const ref = useRef<THREE.Mesh>(null!)
useFrame((_, dt) => { ref.current.rotation.y += dt })
return (
<mesh ref={ref}>
<boxGeometry />
<meshStandardMaterial color="orange" />
</mesh>
)
}
<Canvas>
<ambientLight intensity={0.6} />
<directionalLight position={[3, 5, 4]} />
<Box />
</Canvas>
R3F が React にもたらす本質
R3F の本質は単に「JSX で書ける」だけではない。React のメンタルモデルがそのまま 3D に適用できる:
- 宣言的 — シーンの状態を 「state の関数」として書ける。
active ? <Red /> : <Blue />のような分岐が自然。 - コンポーネント合成 —
<Robot>というコンポーネントを作って、複数のシーンで使い回せる。 - Suspense — GLTF や HDR の非同期ロードを境界として扱える。「読み込み中はスケルトン、完了したら表示」が自然に書ける。
- Concurrent / Transition —
useTransitionや priority を使って、重いシーン更新を裏で進められる。 - エコシステム — Zustand / Jotai / React Query / Tanstack Router 等、React 周辺のすべてが使える。
- HMR — Vite/Next.js のホットリロードがそのまま効く。
裏側: Reconciler が何をしているか
R3F は react-reconciler をカスタムレンダラとして実装している。 DOM の代わりに Three.js のオブジェクトを「ホスト」として扱う。
- JSX の type(
"mesh"など)を見て、対応する Three.js クラス(THREE.Mesh)をnewする。 - props をインスタンスのフィールドに代入する(
position={[1,2,3]}→mesh.position.set(1,2,3))。 - 子要素は
scene.add(child)または「attach」される。 - props が変わると、対応するフィールドが書き換えられる(再レンダリングの代わりにミューテーション)。
- JSX ツリーから外れたインスタンスは
dispose()される(自動メモリ管理)。
この仕組みのおかげで、「Three.js のコードを React で書いている」のではなく「React の世界が Three.js を含んでいる」感覚になる。 逆に Three.js の概念(マテリアル・ジオメトリ・ライト)はそのまま頭に入っている必要がある。
30秒のメンタルモデル
- JSX 要素は Three.js のクラス。
<mesh>=new THREE.Mesh()、<ambientLight>=new THREE.AmbientLight()。要素名の最初を小文字にしただけ。 argsがコンストラクタ引数。<boxGeometry args={[2,1,1]} />=new BoxGeometry(2,1,1)。- props はオブジェクトのプロパティ。
position/rotation/scaleは[x,y,z]の配列で渡せる。 - 子要素は
add()される。geometry/material 等はattach経由で親にぶら下がる(普通は自動)。 - 毎フレーム動かしたいときは
useFrame。React の再レンダーは挟まず、ref を直接ミューテートする。 - イベントは普通の React イベント。
onClick/onPointerOverをメッシュに直接書ける。 - 非同期ロードは
useLoader。Suspense と統合される。
R3F のエコシステム地図
R3F は単独で使うことはまずない。周辺ライブラリと組み合わせて「R3F スタック」になる。
| ライブラリ | 役割 |
|---|---|
| @react-three/drei | 定番ヘルパー集(OrbitControls, Environment, useGLTF, Html, Text 等) |
| @react-three/postprocessing | Bloom / DoF / Vignette などのポストエフェクト |
| @react-three/rapier | 物理シミュレーション(Rust 製の Rapier) |
| @react-three/cannon | 物理(Cannon.js)。レガシー対応で残る |
| @react-three/xr | WebXR(VR/AR)対応 |
| @react-three/csg | ブール演算(メッシュの union/subtract/intersect) |
| @react-three/uikit | 3D 空間内の UI(Yoga/Tailwind 風) |
| leva | リアルタイム調整 UI(GUI で値を弄れる) |
| @react-spring/three | 宣言的バネアニメ |
| framer-motion-3d | framer-motion の 3D 拡張 |
| gltfjsx | GLB → JSX 自動変換 CLI |
| zustand | R3F と相性最高の状態管理 |
R3F vs 生 Three.js
| R3F | 生 Three.js | |
|---|---|---|
| 記述スタイル | 宣言的(JSX) | 命令的(new / add / dispose) |
| 状態管理との連携 | React の世界がそのまま | 自分で書く |
| HMR | ○(Vite/Next.js) | シーンの再構築コスト大 |
| 非同期アセット | Suspense と統合 | 自分で進捗管理 |
| ボイラープレート | 少ない | 多い(毎回 setup) |
| 低レベル制御 | refs 経由で同等 | 直接 |
| 学習コスト | Three.js + React + R3F 流儀 | Three.js だけ |
| 世の中の参考実装 | 増えてきた | 圧倒的に多い |
使うと嬉しい場面
- UI と 3D が混在するアプリ(製品ビューワ、コンフィギュレータ、ダッシュボード)
- 状態と 3D が連動する(フォーム値で見た目が変わる、選択中をハイライト)
- コンポーネント単位で再利用したい(ボタン的な mesh コンポーネント)
- Suspense と相性がいい(GLTF のロードを境界として扱える)
- プロトタイプから本番まで同じスタックで進む
使わない方が良い場面
- 命令的なリアルタイムゲームループの低レベル制御が必要 — 素の Three.js の方が向く。
- 毎フレーム100万頂点を CPU から書き換えるような extreme なケース — reconciler のオーバーヘッドが見える。
- React を使わない既存プロジェクトに混ぜたい — 単なる 3D 部品なら素の Three.js でいい。
R3F はあくまで Three.js の薄いラッパ。Three.js の基本概念(scene / camera / mesh / material /
geometry / light)が頭に入っていることが土台。
土台の知識は Three.js のセクションを参照。
2026 春時点のバージョン感
- @react-three/fiber 9.x / @react-three/drei 10.x
- React 19 対応、Concurrent Mode 対応
- Three.js は r170+ ベース
- WebGPU バックエンド(
WebGPURenderer)も実験的に使える - React Native でも動く(
react-three-fiber/native+ Expo)
導入
npm install three @react-three/fiber
# よく使うヘルパー
npm install @react-three/drei
# TypeScript なら(最近の three は型同梱なので不要なケース増えてる)
npm install -D @types/three
Vite テンプレート
npm create vite@latest my-3d -- --template react-ts
cd my-3d
npm install three @react-three/fiber @react-three/drei
npm run dev
Next.js (App Router)
Canvas はクライアント専用なので "use client" が必須。詳しくは パフォーマンス・注意点。
このノートの読み方
- 基本(Canvas) で
<Canvas>の役割と JSX→Three の対応を押さえる。 - フック で
useFrame/useThree/useLoaderを覚える。ここがR3Fの本体。 - シーン構築 でライト・マテリアル・カメラを実用例で見る。
- 動かしたいなら アニメーション、当たり判定が要るなら インタラクション。
- 表現を伸ばしたい時に シェーダ・ポストプロセス。
- 物理が要るなら 物理シミュレーション。
- Meta Quest など HMD で没入型を作るなら WebXR / VR / AR。
- 定型は drei とアセットに揃っている。
- 本番投入前に パフォーマンス・注意点 を一通り読む。