It’s now or never

IT系の技術ブログです。気になったこと、勉強したことを備忘録的にまとめて行きます。

useTransitionについて理解する

useTransitionとは

https://react.dev/reference/react/useTransition

useTransitionは、React 18で新しく導入されたhooks。

useTransitionは、startTransitionという関数を提供していて、このstartTransitionに渡した関数内で状態を更新された場合、その状態更新でのレンダリングはノンブロッキングになる。

主な目的としては、レンダリングに非常に重い(遅い)処理がある画面レンダリングを遅延して、レンダリングが完了してから表示するために利用する。

レンダリングに遅い処理がある場合、何も対応しないとその間画面操作がブロッキングされることがある。

■ useTransitionを使わないときの処理の流れ

  1. setStateで値を更新
  2. 新しい状態で画面をレンダリング
  3. レンダリング中に遅い処理を実行(この間ユーザーは、操作ブロッキングされる)
  4. 処理後、画面が表示されユーザーが操作可能になる。

■ useTransactionを使ったときの流れ

  1. startTransitionを使ってsetStateで値を更新
  2. 画面表示は変わらず(ペンディング状態)、裏側でレンダリングが実行される。(この間ユーザーは、画面操作が可能)
  3. 画面レンダリングが完了した時点で画面が切り替わる

この裏側でレンダリング中に別の状態更新が起こった場合は、裏側のレンダリングをReactがキャンセルしてくれる。

使い方

function TabContainer() {
  const [isPending, startTransition] = useTransition();
  const [tab, setTab] = useState('about');

  function selectTab(nextTab) {
    startTransition(() => {
      setTab(nextTab);
    });
  }
}

[isPending, startTransition] = useTransition();

useTransitionは、2 つの値を配列として返す。

startTransitionに渡された関数内で状態の更新が発生すると、その更新はトランジションとしてマークされ、画面表示はレンダリング完了まで遅延するようになる。

https://react.dev/reference/react/useTransition#examples

実際の挙動の違いは、公式のサンプルを比較するのがわかりやすい。

startTransitionを使うことででUIブロッキングを防ぐことができる。

Suspense コンポーネントとの組み合わせ

<Suspense fallback={'loading...'}>
  <Router />
</Suspense>

startTransitionを使うと、内部でPromiseがthrowされている状態(ペンディング状態)でもSuspenseのfallbackコンポーネントは表示されない。

startTransitionの遅延レンダリングが優先され、画面には更新前の画面が表示される。

(isPendingによる読み込み表示は可能)

基本的にSuspense内で画面切り替えを行うときは、startTransitionを使うことを推奨されている。

理由は、以下2点とのこと。

  • 画面遷移中にユーザーが別操作で遷移を中断することが可能
  • 遷移のたびに、全体のローディングを表示するとUXを下げるため

useDeferredValue との違い

https://react.dev/reference/react/useDeferredValue

同じような役割として、useDeferredValueがある。

こちらは、変更対象の値をマークしていて、その値を使ってレンダリングが発生したときにレンダリング完了まで画面表示を遅延させてくれる。

inon29.hateblo.jp

目的は、似ているが、useTransitionは画面遷移などの状態更新で使う。useDeferredValueはテキストBoxなどの入力値に対する再レンダリングで使用する。

(同じことをどちらでもできるシーンはありそうだが、このあたりは実際に使って理解していきたい)

その他注意事項

startTransition内で実行する状態更新は同期でなくてはならない

startTransition(() => {
  setTimeout(() => {
    setPage('/abou');
  }, 1000);
});

上記のようにstartTransition内の状態変更は同期処理でないとトランジションとしてマークされない。

startTransitionにわたす関数は同期実行される

console.log(1);
startTransition(() => {
  console.log(2);
  setPage('/about');
});
console.log(3);

このログは、1, 2, 3の順で表示される。

つまり、startTransition内の関数もstartTransition内で同期実行される。