Suspense for Data Fetching
dataのfetch中に fallback
で指定したものを表示する
Componentがloading中の表示の責務を負わなくなった
fetchしたデータを返す関数の返り値が、 T|undefined
から T
になった
これは、
try..catch
の文脈で内部でthrowする関数の返り値が
T|undefined
から
T
になったのと同じ

suspendする操作は、基本的にはlibraryやframeworkがやってくれる
そのため、自分でpromiseをthrowすることは稀なはず

使用時に気にする点
どのlibraryやframeworkがsuspenseに対応しているか
それらは isLoading
のような値を返す代わりに、内部でpromiseをthrowする
仕様を確認すべきだが対応していない場合は、
|undefined
の型になるはずなのでこれは気づけるはず

try..catch
と同じで、どれがthrowするのかを型で判別できない
内部でthrowしている可能性も踏まえて囲んでいかないといけない
使用例
登場人物は3つ
内部でpromiseをthrowするfetch機能
fetchしたデータを使用する子Component
fallbackなどを制御する親Component
fetch機能と、それを使用する子Component
tsconst 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なら勝手にそうなっている
着目すべき点は、
useData()
の返り値は、 string|undefined
ではなく、 string
である
Child
の中では if(data==null) {..}
の処理がない
データが存在する場合のViewの表示のみが責務とできる
fallbackなどを制御する親Component
Child
の準備がまだできていない時は loading..
を表示する
ts<Suspense fallback={<div>loading..</div>}>
<Child />
</Suspense>
従来はどうしていたか
fetchしてきたデータをstateに入れたり、
ReduxでREQUEST, SUCCESS, FAILUREのように3つ定義していたが、それも不要になった
tsconst Child: React.FC = () => {
const data = useData(); // data :: string|undefined
if(data == null) return null;
return <div>Data is {data}</div>;
};
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>
を囲う場所を変更する
ちゃんと理解してない

親の親とかをかこったりしたらなおった
なんで?
なんか条件がありそう
Componentの中でswitch的な分岐してるとか
投げられたpromiseが
settledになることで再renderingする
そのpromiseがfulfilledではなく、rejectされたらどうなる
#??errorがthrowされる
Suspenese内の処理でerrorが起きた時にどうするか
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が始まらない