6
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

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

Last updated at Posted at 2023-04-08

さて、今更感ありますが

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

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

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

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

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を作成し、動かしてみます。

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

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

  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)
      })
    })
  }

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

Suspense(Concurrent Rendering)

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

ルールは2つで

  • SuspenseさせたいコンポーネントはPromiseをthrowすること
  • ↑をSuspenseコンポーネントで囲い、fallbackを指定すること
// 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

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

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

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

Suspenseを使わない場合は、

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

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

まとめ

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


参考

6
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?