React 16 では Fiber と呼ばれるレンダリングエンジンの完全な書き直しが行われました。公開 API はほぼ変わりませんが、内部のスケジューリングモデルは根本的に異なります。
React 15 の問題
React 15 のリコンサイラは同期的で中断不可能でした。リコンサイルを開始すると、ツリー全体が完了するまでブラウザに制御を返しませんでした。
複雑なコンポーネントツリー(数千ノード)では、リコンサイルに 50-100ms かかることがありました。その間:
- ブラウザはユーザーイベントに応答できない
- アニメーションがカクつく
- 入力にラグを感じる
これは「ジャンク」と呼ばれ、メインスレッドが独占されていました。
Fiber の解決策
Fiber はリコンサイルを小さな作業単位(Fiber ノード)に分割し、一時停止・再開が可能にします:
React 15: [=== リコンサイル 100ms ===] → コミット → レンダー
ブラウザ:[リコンサイル中はブロック]
React 16 Fiber:
[5ms] → [ブラウザに制御を返す] → [5ms] → ... → コミット → レンダー
ブラウザ:途中でユーザー入力やアニメーションを処理できる
2つのフェーズ
Fiber は作業を2つのフェーズに分離します:
フェーズ 1:リコンサイル(非同期、中断可能)
- 「作業中」の Fiber ツリーを構築
- 古いツリーと新しいツリーを差分比較
- 変更を収集(これが一時停止・中断できる部分)
フェーズ 2:コミット(同期、中断不可能)
- すべての DOM 変更を一括で適用
- レイアウトエフェクト、ref 更新を実行
- ユーザーが部分的な状態を見ないよう、同期的である必要がある
フェーズ 1 は中断される可能性があるため、フェーズ 1 で呼ばれるライフサイクルメソッドは複数回実行される可能性があります。これが componentWillMount、componentWillReceiveProps、componentWillUpdate が非推奨になった理由です。
Fiber ノードの構造
javascript
// 簡略化した Fiber ノード
{
type: 'div',
stateNode: domNode, // 実際の DOM ノード
child: fiberNode, // 最初の子
sibling: fiberNode, // 次の兄弟
return: fiberNode, // 親
effectTag: 'UPDATE', // 必要な操作
alternate: fiberNode, // 対応する Fiber への参照
}
優先度スケジューリング
Fiber は更新の優先度システムを導入します:
- 同期:今すぐ必要(ユーザー入力)
- アニメーション:次のフレーム
- 高:すぐに
- 低:遅延可能
高優先度の更新(入力など)は低優先度の更新(大規模な再レンダー)を中断して先に適用できます。
Fiber で可能になった機能
React 16 では Fiber の書き直しの結果としてこれらの機能が追加されました:
- エラーバウンダリ — componentDidCatch
- フラグメント — render から配列を返す
- ポータル — 親 DOM ノード外でレンダー
- SSR パフォーマンスの改善
Concurrent Mode や Suspense などのより大きな機能は将来のリリースに取っておかれましたが、Fiber があって初めて実現可能になりました。