現在 React Fiber には Lanes という概念が導入されています。シンプルにいえば Lanes はタスクの優先度を表現したビットマスクです。 この Lanes が既存のアプローチで困難だった複数の優先度の扱いをどのように解決するか、この PR の作成者 acdlite が解説してくれています。
これは包括的な記述ではありません、包括的な記述は多くのページを必要とします。ここでは簡潔な概略を示すことでReactチームのメンバーが旧モデル(Expiration Times)から新モデル(Lanes)に移行するのを助けることを目的としています。続く開発ステップが終われば、より長い技術ドキュメントをもう一度書くことができます。
Lanes モデルが Expiration Times モデルより優れている点は、主に2つあります。
- Lanes は優先順位付け(「タスクAはタスクBより優先度が高いか?」)とバッチング(「Aはこのタスク群に含まれるか?」)の概念を切り離します。
- Lanes は32-bit 型によって、多数の異なるスレッドを表現できます。
const isTaskIncludedInBatch = priorityOfTask >= priorityOfBatch;
旧モデルでは、あるworkユニットを作業中のバッチに含めるか決めるために、バッチとworkの相対的な優先順位を比較していました。
const isTaskIncludedInBatch = priorityOfTask >= priorityOfBatch;
これがうまくいっていたのは、低い優先度のタスクは、より高い優先度のタスクが含まれていなければ完了できないという制約を課していたからです。優先順位 A > B > C のとき、A をやらずに B に取り組むことは出来ませんし、A と B の両方をやらずに C に取り組むことをできません。
この制約はサスペンスが登場する前に設計されたもので、そのときは意味がありました。作業がすべてCPUバウンドであれば、優先度以外の順番でタスクに取り組む理由はあまりありません。しかしIOバウンド (すなわちサスペンス) のタスクを導入すると、IOバウンドで高優先度のタスクが低優先度のCPUバウンドのタスクの完了を妨げるということがありえます。
Expiration Timesの同様の欠点は、複数の優先度グループを表現する方法が限られていることです。
Set object を使うことはメモリーや計算の観点から実践的ではない -- 我々が取り組む存在チェックはどこにでもあるため、そのチェックは高速でなるべく省メモリな必要があります。
妥協の結果、セットの代わりに、優先度レベルの範囲をメンテナンスすることが多いです。
const isTaskIncludedInBatch = taskPriority <= highestPriorityInRange && taskPriority >= lowestPriorityInRange;
2つの独立したフィールドを設定することは措いても、これはかなり表現力が限定的です。閉じて連続的なタスクの範囲を表現することはできます。しかし別々のタスクの有限[finite]セットを表現することはできません。たとえば、タスクの範囲があるとき、真ん中の優先度のタスクをどうやって除去しますか?ちゃんとした回避策を用意したとしても、このようにタスクのグループを推論することは、ひどい混乱を生み出し、prone to regressions[逆行の傾向がある]。
当初、意図的に、しかし以降はby accident[誤って]、1) 優先度 2) バッチング という2つの概念を1つの概念にまとめていました。我々は、片方の概念に影響しない言葉でもう片方の概念を表現することができませんでした。
const isTaskIncludedInBatch = (task & batchOfTasks) !== 0;
新しいモデルでは、この2つの概念を切り離します。タスクのグループは、相対的数値ではなく、ビットマスクとして表わされます。
タスクを表すビットマスクの種類をLaneと呼びます。バッチを表すビットマスクの種類をLanesと呼びます。(注意:これらの名前は最終版ではないです。複数形は多少混乱を招くとわかっていますが、この名前があちこちに登場するので、短い名前にしたかったのです。でも、提案は受け付けています。)
より具体的なReact用語では、setState
によってスケジュールされた更新はlane
フィールドを持ち、このlane
は1つのビットが立っています。これは旧モデルのupdate.expirationTime
フィールドを置き換えます
一方で、fiberは1つのアップデートだけでなく、潜在的には多くのアップデートに紐付いている可能性があります。そのためfiberは、0個以上のビットが立っているビットマスクlanes
フィールドをもっています。(旧モデルのfiber.expirationTime
)。そして childLanes
fieldも持っています (旧モデルの fiber.childExpirationTime
)。
Lanesはopaque型1です。ReactFiberLaneモジュール内でのみ、ビットマスクの直接操作を行うことができます。他の場所では、そのモジュールからヘルパー関数をインポートする必要があります。これはトレードオフですが、最終的には価値のあるものだと思います。というのも、Laneの扱いは非常にとらえがたく、すべてのロジックを同じ場所に設置することで、(今回のような)大規模なリファクタリングを毎回行うことなく、ヒューリスティックな調整を容易にすることができるからです。
Commonly seen ExpiratTime fields, translated to Lanes
renderExpirationtime
->renderLanes
update.expirationTime
->update.lane
fiber.expirationTime
->fiber.lanes
fiber.childExpirationTime
->fiber.childLanes
root.firstPendingTime
androot.lastPendingTime
->fiber.pendingLanes
https://qiita.com/omochimetaru/items/f13fe3e54fab01648ba4[^1]