ReactのSuspenseとuseTransition()を試してみた。

React 18 で追加されたSuspenseuseTransition()についてです。
コードだけでは分かりづらいし試すのも面倒だったので実行結果付きで載せてます。
実行結果を先に見たい人はスクロールしてください。

Suspense

まずはSuspenseを試します。

Service.tsというサービスへのアクセスを模擬したファイルを用意します(3秒ラグを模擬します。)。

YukkuriComponent.tsxKossoriComponent.tsxという二つのコンポーネントを用意

SuspenseComponent.tsxYukkuriComponentKossoriComponentを切り替える親コンポネントです。

Service.ts

const sleep = (timespan: number) =>
{
    return new Promise<void>(r => setTimeout(r, timespan));
}

export const loadAbcdItems: () => Promise<string[]> = async () =>
{
    await sleep(3000);

    return ['AAA', 'BBB', 'CCC', 'DDD'];
}

export const loadXyzItems: () => Promise<string[]> = async () =>
{
    await sleep(3000);

    return ['XXX', 'YYY', 'ZZZ'];
}

loadAbcdItems()loadXyzItems()はそれぞれデータを取得するのに3秒掛かるようになってます。

YukkuriComponent.tsx と KossoriComponent.tsx

import { loadAbcdItems } from "./Service";

let items: string[] = [];

export const YukkuriComponent = () =>
{
    if(items.length)
    {
        return (
            <ul>
                {
                    items.map(_ => <li key={_}>{_}</li>)
                }
            </ul>
        )
    }

    throw new Promise<void>(async r => {
        items = await loadAbcdItems();
        r();
    });

}

export default YukkuriComponent;

console.log("Load Yukkuri Component");
import { loadXyzItems } from "./Service";

let items: string[] = [];

export const KossoriComponent = () =>
{
    if(items.length)
    {
        return (
            <ul>
                {
                    items.map(_ => <li key={_}>{_}</li>)
                }
            </ul>
        )
    }

    throw new Promise<void>(async r => {
        items = await loadXyzItems();
        r();
    });

}

export default KossoriComponent;

console.log("Load Kossori Component");

この二つのコンポーネントはほとんど同じことをしています。
YukkuriComponentはloadAbcdItems()を表示します。
KossoriComponentはloadXyzItems()を表示します。

変わったところは、まだデータが取得されていない状態ではPromiseをスローするということです。
loadAbcdItems() / loadXyzItems()が終了したら再びレンダリングされ、itemsの一覧が表示されます。

条件にitems.length0の時は例外をスローしてますが、実際にはitemsが0でもいい場合を考慮してません。
その場合はバグになるので適当にnull判定をするなど対策してください。

SuspenseComponent.tsx

import React, { Suspense, useState } from "react";

const YukkuriComponent = React.lazy(() => import('./YukkuriComponent'));
const KossoriComponent = React.lazy(() => import('./KossoriComponent'));

const Loading = () =>
{
    return <p>Loading...</p>
}

const SuspenseComponent = () =>
{
    const [flag, setFlag] = useState(true);

    return (
        <>
            <input type="button" value="change" onClick={e => setFlag(!flag)} />

            <Suspense fallback={<Loading />}>
                {
                    flag ? <YukkuriComponent /> : <KossoriComponent />
                }
            </Suspense>
        </>
    )
}

export default SuspenseComponent;

loadAbcdItems() / loadXyzItems()が実行中の間Suspenseコンポーネントのfallbackで指定したコンポーネントが表示されます。

実行結果。

実行結果は次のようになります。

ページを更新するとLoading...が表示されたあとloadAbcdItems()の結果が表示されます。
changeボタンを押すと再びLoading...が表示された後loadXyzItems()が表示されます。

ページの更新を確認するためにCodeSandboxの回転矢印をクリックして更新(Reflesh preview)してみてください。

useTransition()

変化をわかりやすくするため先に結果を表示します。

changeをクリックすると何かがかわってます。
クリックしてもLoading...は表示されずYukkuriComponentが表示され続け、およそ3秒後KossoriComponentが表示されました。

変更箇所はSuspenseComponent.tsxです。

import React, { Suspense, useState, useTransition } from "react";

const YukkuriComponent = React.lazy(() => import("./YukkuriComponent"));
const KossoriComponent = React.lazy(() => import("./KossoriComponent"));

const Loading = () => {
  return <p>Loading...</p>;
};

const SuspenseComponent = () => {
  const [flag, setFlag] = useState(true);
  const [isPending, startTransition] = useTransition();

  return (
    <>
      <p>{isPending ? "pending pending pending" : "yukkuri kossori change"}</p>

      <input
        type="button"
        value="change"
        onClick={(e) => startTransition(() => setFlag(!flag))}
      />

      <Suspense fallback={<Loading />}>
        {flag ? <YukkuriComponent /> : <KossoriComponent />}
      </Suspense>
    </>
  );
};

export default SuspenseComponent;

変更部分は次の2箇所です

{isPending ? "pending pending pending" : "yukkuri kossori change"}

onClick={(e) => startTransition(() => setFlag(!flag))

状態の変更をstartTransition()のコールバック内で行ってます。
KossoriComponentが描画出来るまで現在のDOMが表示されるようになります。

isPendingは次が描画出来るまでtrueになります。

BlockEditor certificate css DataGrid Docker Gutenberg Hyper-V iframe MUI openssl PHP React ReduxToolkit REST ubuntu WordPress オレオレ認証局 フレームワーク