React18の新機能について知る機会があり、それを参考に自分でもまとめてみた
新機能は以下
- Automatic Batching
- Transitions
- Suspense and SSR
Automatic Batching
要約
React18からはBatching対象が広がる
詳細
Batchingは複数のstateを更新したときにレンダリングを1回にまとめる仕組み
例えば、下のコードではsetCountとsetClickedの2回レンダリングされるのではなく、handleClickで1回のレンダリングにまとめられる
count [count, setCount] = useState(0); count [clicked, setClicked] = useState(false); function handleClick() { setCount(count + 1); setClicked(true); }
しかし、React17まではpromiseやcallback、timeoutはBatching対象でないので2回レンダリングされていた
fetch(...).then(() => { setCount(count + 1); setClicked(true); }); element.addEventListener('click', () => { setCount(count + 1); setClicked(true); }); setTimeout(() => { setCount(count + 1); setClicked(true); }, 1000);
これらがReact18からはBatching対象となる
これにより、これまで明示的にオプトインしていたReactDOM.unstable_batchedUpdates()
は不要になり、逆に、オプトアウトしたい場合にReactDOM.flushSync()
を使う
下の例では2回レンダリングが行われることになる
import { flushSync } from 'react-dom'; function handleClick() { flushSync(() => { setCount(count + 1); // 再レンダリング }); // DOMの更新 flushSync(() => { setClicked(true); // 再レンダリング }); }
Transitions
要約
緊急度の高い更新と低い更新を分離する
詳細
例えば、リストの絞り込み機能
下のようなリストと入力フォームがあったとして
[ ] - chocolate - toast - tomato
「to」と入力した場合、数秒後にchocolateが除外されても特に問題はない
[ to ] - chocolate - toast - tomato
...
[ to ] - toast - tomato
このとき、入力フォームのタイプされた文字は緊急度の高い更新だが、リストの更新は緊急度が低いといえる
Transitionsは緊急度の高い更新を緊急度の低い更新が邪魔しないように、緊急度の低い更新をバックグラウンドで行い、緊急度の高い更新が終わったら反映するしくみ
import { startTransition} from 'react'; // 緊急度が高い: タイプされた文字を表示 setInputValue(input); startTransition(() => { // 緊急度が低い: 結果を表示 setSearchQuery(input); });
緊急度が低い更新を開始するにはstartTransition()
を使う
また、新しいTransitionが始まると以前のTransitionは破棄される
Suspense and SSR
要約
SSRの課題を解消するStreaming HTML と Selective Hydration の機能ができた
Streaming HTMLはサーバーが細かい単位でコンポーネントを送信することができる仕組み Selective Hydrationはクライアントサイドがハイドレーションを細分化する仕組み
これらによりSuspenseの外側と内側でレンダリングが分割された
詳細
これまでのSSRの次のようなウォーターフォールの課題を解消する
- サーバーサイドのデータフェッチが終わらないとレンダリングが始まらない
- サーバーサイドでレンダリング全体が終わらないとレスポンス送信できない
- クライアントサイドでブラウザがHTML全体をレンダリングしないとハイドレーションが始まらない
- コンポーネントツリー全体がハイドレーションされないとインタラクティブにならない、補助的なコンテンツのデータフェッチが始まらない
仕組みとしては<Suspense>
を使う
まず、<Suspense>
で囲まれたコンテンツのデータフェッチを待たずにサーバーサイドからHTML出力される
その間、<Suspense>
内はローディング中になっているが、SSRはそれで終わりではなく
データフェッチが終わった時点で次のような HTML と JavaScript がサーバーから送られ、前に送られてきた HTMLを更新する(Streaming HTML)
<div hidden id="comments"> <!-- Comments --> <p>First comment</p> <p>Second comment</p> </div> <script> // This implementation is slightly simplified document .getElementById("sections-spinner") .replaceChildren(document.getElementById("comments")); </script>
クライアント側は<Suspense>
以外の部分を先にハイドレーションし、まだ読み込みが済んでいない部分と分離することができるので、ページをレンダリングするために必要な JavaScript コードが全部読み込まれないとハイドレーションを行えないという問題が解消される(Selective Hydration)