Reactで並列処理?並行レンダリング

# はじめに
React 18 の useTransition + Suspense を使うことでReactの並列レンダリング（concurrent rendering）機能を活用し、ユーザー体験を損なわないスムーズなUI更新を実現する機能を試すことのできるコードを紹介します。

:::note info
**ポイント: startTransition を使った並列処理**
```typescript
/*
 * useTransition() で「重い処理は後でやる」宣言ができる。
 * startTransition(() => {...}) で囲まれた処理は「低優先度」で実行される。
 * → ユーザーのクリックなどの高優先度の処理を先に行う。
 */
 const [isPending, startTransition] = useTransition();

 function updateFilter(newFilter){
  // UIの状態は即座に変える（見た目が速い）
  setFilter(newFilter);

  // 重いデータ取得は後から、しかも遅延してレンダリング
  startTransition(() => {
    fetchData(newFilter);
  });
}
/*
 * フィルターの変更は即時 (setFilter)
 * データ取得は startTransition() の中 → UIの応答性を維持
 */

```
以下のようなユースケースで活躍しそうです。
・フィルター切り替え、検索結果表示、タブ切替など、操作直後にUIの反応が求められる処理
・**重たいデータフェッチや描画** を「非同期にしたいけれど、**入力はすぐに反映したい** 」場面
・React 18以降を使っている環境

:::

# サンプル

```tsx:App.tsx
import React, { useState, useTransition, Suspense } from "react";

// フェイクAPI：フィルターに応じたデータ取得
function fetchData(filter: FilterType): Promise<string[]> {
  return new Promise((resolve) => {
    setTimeout(() => {
      const data =
        filter === "favorites"
          ? ["りんご", "バナナ"]
          : ["りんご", "バナナ", "みかん", "ぶどう"];
      resolve(data);
    }, 1000);
  });
}

// 型定義
type FilterType = "all" | "favorites";

// サスペンス対応のラッパー
function createResource<T>(promise: Promise<T>) {
  let status: "pending" | "success" | "error" = "pending";
  let result: T;
  let error: any;

  const suspender = promise.then(
    (res) => {
      status = "success";
      result = res;
    },
    (err) => {
      status = "error";
      error = err;
    }
  );

  return {
    read(): T {
      if (status === "pending") {
        throw suspender;
      } else if (status === "error") {
        throw error;
      } else {
        return result!;
      }
    },
  };
}

// グローバルリソース
let resource = createResource(fetchData("all"));

// フィルター更新に応じてリソースを再生成
function loadResource(filter: FilterType) {
  resource = createResource(fetchData(filter));
}

// 結果表示コンポーネント
function Results({ filter }: { filter: FilterType }) {
  const items = resource.read();
  return (
    <ul>
      {items.map((item) => (
        <li key={item}>{item}</li>
      ))}
    </ul>
  );
}

// メインコンポーネント
export default function App() {
  const [filter, setFilter] = useState<FilterType>("all");
  const [isPending, startTransition] = useTransition();

  function updateFilter(newFilter: FilterType) {
    setFilter(newFilter);
    startTransition(() => {
      loadResource(newFilter);
    });
  }

  return (
    <div className="dashboard">
      <nav className="filter-bar">
        <button
          className={filter === "all" ? "active" : ""}
          onClick={() => updateFilter("all")}
        >
          すべて
        </button>
        <button
          className={filter === "favorites" ? "active" : ""}
          onClick={() => updateFilter("favorites")}
        >
          お気に入り
        </button>
        {isPending && <span className="loading-indicator">更新中...</span>}
      </nav>

      <div className="content">
        <Suspense fallback={<div className="skeleton-loader">読み込み中...</div>}>
          <Results filter={filter} />
        </Suspense>
      </div>
    </div>
  );
}
```




## スタイル（お好みで）
```css:App.css
.dashboard {
  font-family: sans-serif;
  padding: 20px;
}
.filter-bar {
  margin-bottom: 10px;
}
button {
  margin-right: 10px;
}
.active {
  font-weight: bold;
}
.loading-indicator {
  margin-left: 10px;
  color: blue;
}
.skeleton-loader {
  opacity: 0.6;
}
```

実行方法（Vite）
```shell
npm create vite@latest my-app -- --template react-ts
cd my-app
npm install
npm run dev
```