一、問題#
JavaScript
エンジンとページのレンダリングエンジンの 2 つのスレッドは排他的です。1 つのスレッドが実行されている間、もう 1 つのスレッドは待機するしかありません。
JavaScript
スレッドが長時間メインスレッドを占有している場合、レンダリングの更新は長時間待機する必要があります。画面が長時間更新されないため、ページの応答性が低下し、ユーザーは遅延を感じるかもしれません。
これはReact 15
のStack Reconciler
が直面する問題でもあります。React
がコンポーネントをレンダリングする際、開始からレンダリングが完了するまでのプロセスは中断できません。
コンポーネントが大きい場合、js
スレッドは常に実行され、全体のVDOM
ツリーの計算が完了するまでレンダリングスレッドに渡されません。
これにより、ユーザーのインタラクションやアニメーションなどのタスクが即座に処理されないため、遅延が発生する可能性があります。
二、何か#
React Fiber は、Facebook が React に対して 2 年以上かけて行った重要な変更と最適化であり、React のコアアルゴリズムの再実装です。Facebook は React Conf 2017 で React Fiber が React 16 でリリースされることを発表しました。
React では、次の操作を主に行っています。
- 各タスクに優先度を付け、優先度の高いタスクは優先度の低いタスクを中断できます。そして、注意:優先度の低いタスクを再実行します。
- 非同期タスクを追加し、requestIdleCallback API を呼び出してブラウザがアイドル状態のときに実行します。
- DOM の差分ツリーをリンクドリストに変換し、1 つの DOM に 2 つの Fiber(リンクドリスト)が対応し、2 つのキューが対応します。これは中断されたタスクを見つけて再実行するためのものです。
アーキテクチャの観点から見ると、Fiber
はReact
のコアアルゴリズム(調和プロセス)の再実装です。
コーディングの観点から見ると、Fiber
はReact
内部で定義されたデータ構造であり、React 16
の新しいアーキテクチャである仮想 DOM のFiber
ツリーのノード単位です。
Fiber
は単なるJavaScript
オブジェクトであり、要素の情報、要素の更新操作キュー、タイプを含んでいます。データ構造は以下のようになります:
type Fiber = {
// Fiberを識別するためのWorkTagタイプ。主に、現在のFiberがFunctionComponent、ClassComponentなどのどのコンポーネントタイプを表すかを示します。
tag: WorkTag,
// ReactElementのキー
key: null | string,
// ReactElement.type、createElementの最初の引数を呼び出します。
elementType: any,
// このFiberが表すノードのタイプを示します。
type: any,
// このFiberNodeに対応する要素コンポーネントインスタンスを示します。
stateNode: any,
// 処理が完了した後に上に戻るために使用される、Fiberノードツリー内の「親」を指します。
return: Fiber | null,
// 最初の子ノードを指します。
child: Fiber | null,
// 同じ親ノードを指す兄弟構造、兄弟ノードのreturnは同じ親ノードを指します。
sibling: Fiber | null,
index: number,
ref: null | (((handle: mixed) => void) & { _stringRef: ?string }) | RefObject,
// 現在の処理中のコンポーネントpropsオブジェクト
pendingProps: any,
// 前回のレンダリングが完了した後のprops
memoizedProps: any,
// このFiberに関連するコンポーネントのUpdateは、このキューに格納されます。
updateQueue: UpdateQueue<any> | null,
// 前回のレンダリング時のstate
memoizedState: any,
// このFiberが依存するコンテキストを格納するリスト
firstContextDependency: ContextDependency<mixed> | null,
mode: TypeOfMode,
// Effect
// Side Effectを記録するために使用されます。
effectTag: SideEffectTag,
// 次のSide Effectを素早く検索するための単方向リンクリスト
nextEffect: Fiber | null,
// サブツリー内の最初のSide Effect
firstEffect: Fiber | null,
// サブツリー内の最後のSide Effect
lastEffect: Fiber | null,
// タスクが完了する予定の未来の時点を示します。後のバージョンではレーンと改名されました。
expirationTime: ExpirationTime,
// 子ツリーにまだ待機中の変更があるかどうかを迅速に判断するためのものです。
childExpirationTime: ExpirationTime,
// Fiberのバージョンプール、つまりFiberの更新プロセスを記録して復元するためのものです。
alternate: Fiber | null,
}
三、解決方法#
Fiber
はレンダリングの更新プロセスを複数のサブタスクに分割し、一度に少量ずつ実行します。一部のタスクを完了した後、ブラウザに制御権を返し、ブラウザがページのレンダリングを行う時間を与えます。ブラウザが忙しくないときに再び実行します。
つまり、中断と再開が可能であり、再開後に以前の中間状態を再利用することもできます。さらに、異なるタスクに異なる優先度を設定することもできます。各タスクの更新ユニットはReact Element
に対応するFiber
ノードです。
上記の方法はrequestIdleCallback
メソッドに基づいて実装されています。
window.requestIdleCallback()
メソッドは、ブラウザのアイドル期間に呼び出される関数をキューに入れます。これにより、開発者はメインイベントループでバックグラウンドや低優先度の作業を実行できますが、アニメーションや入力の応答などの遅延の重要なイベントに影響を与えません。
まず、React ではタスクを複数のステップに分割し、バッチ処理で完了させます。一部のタスクを完了した後、制御権をブラウザに戻し、ブラウザがページのレンダリングを行う時間を与えます。ブラウザが忙しくないときに再び以前の React の未完了のタスクを続行することができます。これは協調的なスケジューリングと呼ばれます。
この実装プロセスは、Fiber
ノードを使用して行われます。Fiber
ノードは静的なデータ構造として、各Fiber
ノードは対応するReact element
に対応し、コンポーネントのタイプ(関数コンポーネント / クラスコンポーネント / ネイティブコンポーネントなど)や対応する DOM ノードなどの情報を保存します。
動的な作業ユニットとして、各Fiber
ノードは、このコンポーネントの状態の変更や実行する作業を保存します。
各Fiber
ノードには対応するReact element
があり、複数のFiber
ノードは次の 3 つの属性に基づいてツリーを構築します:
// 親Fiberノードを指します。
this.return = null
// 子Fiberノードを指します。
this.child = null
// 右側の最初の兄弟Fiberノードを指します。
this.sibling = null
これらの属性を使用して、次の実行対象を見つけることができます。