useDeferredValueとuseTransitionを特殊なdebounceとして捉える
なんというか、transition周辺のメンタルモデルを構築するのに少し苦労したmrsekut.icon
詳細な挙動はいったんどうでも良くて、
頭の中の挙動と、表面上の挙動とをさっさと一致させたい
その後、レンダリング回数は何回なのか、みたいな細かいところを見ていけば良い
以下は腑に落ちた理解のメモ
厳密な挙動とは一致しないこともあるかもしれない
あくまでも最初の一歩の理解のためのようなもの
その場合は詳細な実装を追えば良い
どちらも状態の更新によるrenderingをdebounceしているようなもの
両者を同じような語彙で表現できればわかりやすいのだが、
いかんせん「transition」は「〇〇な状態更新」を指すので、
deferred valueの方を「transition」という語彙を使って説明ができない
debounce的ではあるが、実際のdebounceとは異なることに注意が必要
通常のdebounceは指定した秒数を基準にして更新頻度を制御することが多い
code:ts
const df = debounce(f, 500);
しかし、transition周りのそれは、renderingに最適化されているイメージをする
最適なrender回数になるように内部で制御されている感じ
具体的には、
関連するsuspendが完了している、とか
他の優先度の高い更新がなくて暇がある、とか
そういうのを基準にして溜まった処理が完了する
通常のdebounceを以下のように捉える
v1,v2,v3,...,v10という値が一気にきたとき、最後のv10だけを採用する
これは、以下のようにも捉えられる
() => v1
() => v2
...
() => v10
という関数の組が一気に来たときに、全てを実行して最後の値を採用している
これをtrantisionでも同じイメージを持つ
この様に書いたとき
code:ts
startTransition(() => {
setCounter((c) => c + 1);
});
以下のような状態の更新関数の組がレンダリングの回数分作られる感じ
s => s1
s1 => s2
....
s9 => s10
という更新関数が蓄積され、基準が満たされたときに全てを実行し、最終的な状態を生成する
ポイントは、debounceで蓄積されるものが、
「クリックしたこと」によって蓄積が増えるのではなく
「renderingしたこと」によって蓄積が増えることだろう
状態更新自体が遅延するのではなく、その状態更新の結果として起こるrenderingが遅延される
なので以下を区別して考える必要がない
その状態を参照している処理が重いパターン
状態更新自体は軽いが、その後の処理が重い
例えば、countを+1して、それを見てfetchするとか
新しい状態を計算するのが重いパターン
重い処理を行ってから、軽い状態更新を行う
例えば、クリックしたらfetchが起きて、その結果で更新するとか
時間が0.1秒ごとにstateを更新している最中に、
1秒かかる処理をtransitionとして更新すると、
debounceが完了するまでに10回renderingされるので、
それが蓄積され、最後にその10個が全て適用される
https://gyazo.com/a7f55b98e4dca12dd826c34c80a56c06
「debounceの反映は、suspend後である」というルールがあるだけ
それによって、
データが読み込まれるまでは、fallbackされることなく以前の値が表示され続ける
これもdebounceのメンタルモデルと同じ様に捉えられる
「TransitionがSuspenseの外に漏れ出ている」という捉え方ではなく、自然に理解できる
あとは、その蓄積中に、途中でrenderingを放棄できる、とかそういうルールが有るぐらい