generated at
Render As You Fetchパターン

>@housecor: Problem: Fetching in useEffect means React components render, then fetch. This can lead to slow network waterfalls.
>Solution: Fetch as you render. Use react-query's prefetching (prefetch in the parent) or use @remix_run which does this by default via nested routes.
>

このアンチパターン(Fetch Then Render)を克服するために生まれた

裏にある理論

要点
Web APIの結果をインメモリでキャッシュして取り回すパターン
キャッシュキーの生成や管理手法は自由で、WeakMapだったり文字列だったり様々
この辺に秩序をもたらす設計論もある
Orvalとか
コンポーネントはマウント時に、キャッシュマネージャーを介し透過的にAPIレスポンスを読み取る
キャッシュがあるとき: キャッシュからレスポンスを得る
キャッシュがないとき: データフェッチして、その結果をキャッシュに格納する
得られるメリット
ReadModel副作用に対して冪等性が得られる
キャッシュの有無に関わらず、コンポーネントのレンダリングの結果は同じになる
コンポーネントの純粋度が高まる = レンダリングの予測性を高める
↓みたいなのを考えなくてよくなる
ユニオン型(union type)によるローディングやエラーハンドリング

#React が出典なはず
React HooksSuspenseを組み合わせる
データフェッチ→レスポンス返ってくるまでsuspend→返ってきたらレンダリング再開
ローディングなどの処理をそのコンポーネントの外に任せることができる
型レベルでローディングの状態を追い出すことができ、宣言的に非同期データを扱える
この流れはAlgebraic Effectsを応用したものと言われている
「コンポーネントがpromiseをthrowする = 継続を取ってこれる例外である」
promiseを一種の規約と見なしで副作用を分離するという一種のExtensible Effects
Readerモナドとかとはまた違う概念っぽい? #わかってないこと

コード例
代表的な実装例でいうとTanStack Query
将来的にはReactが公式にReact cache apiでサポートする予定
>@HFarooq22: @N_Tepluhina
> showing in one function how to solve 5 issues that would’ve taken me a week to figure out (if at all 🙏🏼)
この useQuery に全てが詰め込まれている
1. API呼び出し
3. refetch on window focus
4. place holder
5. stale time
6. loading, error
上記の例ではSuspenseは使われていない
オプトインする形で useQuery のオプションに suspense: true を追加するとSuspenseモードになる
従来との差分
old
親がマウント時にuseEffectでデータフェッチして子にpropsで流していた
レスポンスが返ってくるまではフラグ管理などで子コンポーネントをマウントしないなどで制御する
痛み
考慮する状態が増える
マウント時のレスポンスは空
フェッチ後に保存される
検索条件を変えたら再度フェッチ
並列レンダリング制御が難しい
リクエスト数の増加やパフォーマンスの低下
複数APIを扱う時にローディングやAPIを呼び出す順番やレスポンスの正規化で悩む
ローディング管理などでコンポーネント階層が深くなりがち
after
子がデータフェッチを行い親はSuspenseError Boundaryで非同期をハンドリングする
renderフェーズの処理は走る
データフェッチが終わってから、子がレンダリングされる
考慮する状態が減る
ローディング中はそもそもレンダリングされない
依存フェッチの仕組みによって、イベントハンドラで手続き的な再フェッチ処理をしなくとも良い
useEffectが出てこなくなるので再レンダリング回数も減る
コンポーネント階層を浅くできる

UIT INSIDEReduxからSuspenseに移行したときの話

制約
キャッシュされたレスポンスは結果整合性を持つ

トレードオフ
キャッシュキーによるキャッシュ管理が必要
本質的にキャッシュ管理は難しい
いつキャッシュを破棄する, いつ明示的に再取得するなど
CRUDの状態遷移などでキャッシュが古くなった場合、開発者は明示的にキャッシュ破棄する必要性がある
そうしないとキャッシュ破棄を忘れて古いデータが表示される
管理画面などでは多少速度や利便性を犠牲にしてでも強整合性のほうが向いている場合もある

技術の螺旋を乗り越えてうまれたものなので、一度試してみる価値はある

設計上の利点
コンポーネントの見通しがよくなる
Server Stateからundefinedを消すことができる
Race Conditionを抑えられる
高凝集保守性の高いコンポーネントになる
犠牲的アーキテクチャの実現に役立つ
React > Concurrent Featuresと連携してパフォーマンスに優れた設計が容易
Lifting state upと相性がよく、段階的にスケールが可能
並列レンダリング, ローディング, エラーを親で制御できる

勘所