React 18 AutomaticBatchingとSuspenseを動かして理解する

## さて、今更感ありますが
React18の理解をより深めるため、今回はこの2つテーマに選びました。
知ってるけど実際に試していない！ふわっとした理解のままだった！というかたは是非動かして試してくださいね。

## AutomaticBatching オートマチックバッチング

イベントハンドラーの中で複数のstateを更新する時、レンダリングを1回にまとめてくれる機能となります。
こちらはReact17にて、すでに存在した機能です。
React18で変わった点はというと、Priomiseの内部で行われる処理に対してもbatchingが動くようになった...

ということで、試してみます。

```jsx
const AutomaticBatching = () => {
  const [user, setUser] = useState(0)
  const [count, setCount] = useState(0)

  const clickHandler = () => {
   axios.get('https://jsonplaceholder.typicode.com/users').then((res) => {
      setUser(res.data)
      setCount((count) => count + 1)
    })
  }

  console.log('Automatic Batching')
  console.log(user)
  console.log(count)
  return (
    <div className={styles.center}>
      <div>
        <button onClick={clickHandler}>clickHandler</button>
      </div>
    </div>
  )
}

export default AutomaticBatching
```

適当にpageを作成し、動かしてみます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/129861/fc5aff5b-3efd-705c-e4a9-49c0e2ce0c6a.gif" width="400">

(画質悪くて申し訳ないですが)
ボタンを押してstateを2つ更新しましたが、レンダリングは1度だけだと確認できました。

React17までの挙動を再現することもできます。
先ほどのコードに `flushSync` をimportし、非同期内で囲うだけでOK。

```jsx
  import { flushSync } from 'react-dom' //追加

  const clickHandler = () => {
    axios.get('https://jsonplaceholder.typicode.com/users').then((res) => {
      flushSync(() => {
        setUser(res.data)
      })
      flushSync(() => {
        setCount((count) => count + 1)
      })
    })
  }
```

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/129861/ac5818ac-6486-63c6-4000-197532d96bd7.gif" width="400">

Automatic Batchingが無効化されて、React17までと同じように2回レンダリングされるようになりました。

## Suspense(Concurrent Rendering)

Suspenseはは、データを取得するコンポーネントが非同期にデータをロードするのを待ち、データが利用可能になったら表示を切り替える機能を提供します。

ルールは2つで
- SuspenseさせたいコンポーネントはPromiseをthrowすること
- ↑をSuspenseコンポーネントで囲い、fallbackを指定すること

```jsx
// useSWR,axiosを使用して、1ファイルで行ってしまいますね
import { Suspense } from "react";
import useSWR from "swr";
import axios from "axios";

async function sleep(ms: number) {
  return await new Promise((resolve) => setTimeout(resolve, ms));
}

const fetcher = (url: string) => axios.get(url).then((res) => res.data);

const useFetch = (url: string) => {
  // ↓無理やりPromiseをthrowして、時間かせぎもしてみてね
  // if (Math.random() < 0.5) {
  //   throw sleep(1000);
  // }
  const { data, error } = useSWR(url, fetcher, { suspense: true });
  return { data, error };
};

const UserEmail = () => {
  const { data, error } = useFetch(
    "https://jsonplaceholder.typicode.com/users/1"
  );

  if (error) return <p>取得失敗</p>;
  return <div>Data is {data.email}</div>;
};

export const RenderAsYouFetch = () => {
  return (
    <div className="text-center">
      <Suspense fallback={<p>Loading...userEmail</p>}>
        <UserEmail />
      </Suspense>
    </div>
  );
};

```

無事動きました！！
![download.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/129861/30deaed7-7cdc-5b54-ee7a-dc7b784b0f62.gif)


### Render-as-You-Fetchパターンについて

データをリクエストしている間にコンポーネントのレンダリングを開始し、データがまだ利用できない場合はフォールバックUIを表示すること。

`useSWR`を`suspense: true`し、データがまだ利用できない間、Suspenseが`fallbackのUI`を表示するようにしています。

Suspenseを使わない場合は、

```jsx
const UserEmail = () => {
  ~~
  if (error) return <div>Error...</div>;
  if (!data) return <div>Loading...</div>;
  ~~ 
```

としてあげても`Render-as-You-Fetchパターン`です。

## まとめ

今回は `AutomaticBatching` と `Suspense` について実装しながら理解を深めました。その中で`useSWR`や`Render-as-You-Fetchパターン`も使うことができました。
引き続き、Reactの機能を深ぼるぞ！

---



参考
- [ReactのSuspense対応非同期処理を手書きするハンズオン](https://zenn.dev/uhyo/books/react-concurrent-handson) : 
  - Suspenseの挙動を0から追っていくのでかなりわかりやすい。
  - アンチパターンも記載されておりました。
- [React 18に備えるにはどうすればいいの？　5分で理解する](https://qiita.com/uhyo/items/bbc22022fe846fd2b763)

- swr : https://swr.vercel.app/ja/docs/getting-started