generated at
Suspense for Data Fetching
React.Suspenseを使用したパターンの1つ
dataのfetch中に fallback で指定したものを表示する



Componentがloading中の表示の責務を負わなくなった
fetchしたデータを返す関数の返り値が、 T|undefined から T になった
これは、 try..catch の文脈で内部でthrowする関数の返り値が T|undefined から T になったのと同じmrsekut
suspendする操作は、基本的にはlibraryやframeworkがやってくれる
そのため、自分でpromiseをthrowすることは稀なはずmrsekut


使用時に気にする点
どのlibraryやframeworkがsuspenseに対応しているか
それらは isLoading のような値を返す代わりに、内部でpromiseをthrowする
仕様を確認すべきだが対応していない場合は、 |undefined の型になるはずなのでこれは気づけるはずmrsekut
React.Suspenseで囲うのを忘れない
try..catch と同じで、どれがthrowするのかを型で判別できない
内部でthrowしている可能性も踏まえて囲んでいかないといけない



使用例
登場人物は3つ
内部でpromiseをthrowするfetch機能
fetchしたデータを使用する子Component
fallbackなどを制御する親Component
fetch機能と、それを使用する子Component
ts
const Child: React.FC = () => { const data = useData(); // data :: string return <div>Data is {data}</div>; };
useData() は、内部でdataをfetchしてそれを返す
これが、suspenseに対応した実装になっているものとする
つまり、dataのfetch中に、promiseをthrowする実装になっている
これは自作しても良いし、React v18に対応したlibraryなら勝手にそうなっている
自作するハンズオン記事やreact-queryがその例
着目すべき点は、
useData() の返り値は、 string|undefined ではなく、 string である
Child の中では if(data==null) {..} の処理がない
データが存在する場合のViewの表示のみが責務とできる
fallbackなどを制御する親Component
React.Suspenseを使ってfallbackを制御する
Child の準備がまだできていない時は loading.. を表示する
ts
<Suspense fallback={<div>loading..</div>}> <Child /> </Suspense>



従来はどうしていたか
fetchしてきたデータをstateに入れたり、
ReduxでREQUEST, SUCCESS, FAILUREのように3つ定義していたが、それも不要になった
ts
const Child: React.FC = () => { const data = useData(); // data :: string|undefined if(data == null) return null; return <div>Data is {data}</div>; };




React.Suspenseで囲う粒度
UIに合わせて適当に変えればいい
例 1つだけ囲う
C内のsuspendは、A,Bには影響しない
例えば、Cのloadよりも、Aの方が速く終わるなら、Aが先に表示される
tsx
<A/> <B/> <Suspense> <C> </Suspense>
例 A,B,C,Dのいずれかがsuspendした時は、全てを1まとまりしてfallbackする
tsx
<Suspense fallback=".."> <A/> <B/> <C/> </Suspense>
この時、Aがsuspendを引き起こした時、BもCも再renderingされる
個々にloadingを表示するのかや、どれを同じタイミングで表示すべきなのかなどを考慮する


参考
雑に利用するだけならこれ読めば十分
自分でthrowしてみたりして挙動を確認するハンズオン
React v17時のdocs




<Suspense> を囲う場所を変更する
ちゃんと理解してないmrsekut
親の親とかをかこったりしたらなおった
なんで?
なんか条件がありそう
Componentの中でswitch的な分岐してるとか


投げられたpromiseがsettledになることで再renderingする
そのpromiseがfulfilledではなく、rejectされたらどうなる #??
errorがthrowされる
Suspenese内の処理でerrorが起きた時にどうするか
Error Boundaryで囲っておけばいい
ts
<ErrorBoundary fallback={<h2>Could not fetch posts.</h2>}> <Suspense fallback={<h1>Loading posts...</h1>}> <ProfileTimeline /> </Suspense> </ErrorBoundary>



suspendされた時は、renderingが成功していない
だから、useStateを使った状態のsetとかも破棄される


従来のuseEffectなどを使ったdata fetchと比較すると並行性が上がっている
ツリーの形式で、fetchとrenderingをやると、
親のfetchが終わった後に、子をrenderingして、その中で更にfetchとなる
この時、後者の方のfetchをスタートするタイミングが、親のfetchの終わりのタイミングに依存してしまう
親の方に時間がかかる場合、子の方がかなり遅くなる
suspenseを使った場合は、とりあえずみんなfetchし始めた状態で、終わったものからrenderingするといった事ができるようになる


課題
waterfall問題
2つのリクエストを同じcomopnentでやると直列になってしまう
useで解決される?
fetch on render 問題
親のrenderingが終わってないと子のfetchが始まらない