👨‍🎓

Concurrent Reactの概観をハイレベルに理解する

2022/07/23に公開

この記事について

Reactの並行処理機能は、普通の「実装の詳細」と比べてより重要なものであり、Reactのコアのレンダリングモデルに対する本質的な変更です。ですので並行処理の動作について詳しく知ることがもの凄く重要ということではないにせよ、どのようなものかについて高レベルの概観を知っておくことは有用かもしれません。
React v18.0 – React Blogより引用

Reactのコアのレンダリングモデルに対する本質的な変更である、Concurrent Reactについて、ハイレベルな概観を理解するためにまとめた記事です。

Concurrent Reactは機能ではなく、新しいメカニズム

並行処理は、それ自体が何か機能だというわけではありません。これは、同時にUIの複数のバージョンをReactが準備しておけるようにするための、新たな裏方のメカニズムです。
React v18.0 – React Blogより引用

まず機能ではないというところですが、上記引用の通り、Concurrent Reactは、同時にUIの複数のバージョンをReactが準備しておけるようにするための、新たなメカニズム(Reactの新しいコアレンダリングモデル)のことです。

何が新しいかは、以下になります。

  • 今まで:まとめて、中断されず、かつ同期的にレンダリング
  • Concurrent:レンダリングが中断可能で、かつ非同期的なレンダリング(Concurrent Reactの重要な特性)

この新たなメカニズムにより、
ユーザーはよりスムーズなユーザ体験を得ることができ、
デベロッパーはコンポーネントのロードの状態によるUIの制御を、より宣言的に記述することができるようになります。

では、少し具体的にどのようなものかを理解するために、Concurrent Reactの機能の一つであるSuspenseについて触れていきます。

Suspense

簡単なコード例を用いながら、Suspenseの概念的な説明をします。

<Suspense fallback={<Spinner />}>
  <Comments />
</Suspense>

(React v18.0 – React Blogよりコード引用)

Suspenseは、内部のコンポーネントがロード中でまだレンダリングできない状態を処理します。

前セクションで、レンダリングが中断可能で、非同期的なことがConcurrent Reactの重要な特性と言いましたが、この例では<Comments>がロード中にレンダリングを中断し(レンダリングを中断することを、サスペンドすると言う)、ロード完了まで<Spinner>を代替表示します。そして、ロードが完了したら<Commnets>をレンダリングすることで、UIが一貫して表示されるようになります。(非同期的なレンダリング

次の例です。

function ProfilePage() {
  return (
    <PageLayout>
      <Suspense fallback={<MyProfileSkeleton />}>
        <MyProfile />
      </Suspense>
      <Suspense fallback={<AchievementsSkeleton />}>
        <Achievements />
      </Suspense>
      <Suspense fallback={<OrganizationSkeleton />}>
        <Organizations />
      </Suspense>
      <Suspense fallback={<ContributionsSkeleton />}>
        <Contributions />
      </Suspense>
    </PageLayout>
  );
};

この例ではそれぞれを<Suspense>で囲っています。これにより独立したスケルトン表示を持ち、ローディング完了したところから非同期的に表示することが可能になります。
このように、Suspenseの配置の仕方を変えることで、サスペンドの制御を細かに行うことができます。

Suspenseのユースケース

それでは、Suspenseの具体的なユースケースを見ていきます。

React.lazyを使ったコンポーネントの遅延ローディング

現時点での唯一のユースケースです。(参照:https://ja.reactjs.org/docs/react-api.html#reactsuspense)

// This component is loaded dynamically
const OtherComponent = React.lazy(() => import('./OtherComponent'));

function MyComponent() {
  return (
    // Displays <Spinner> until OtherComponent loads
    <React.Suspense fallback={<Spinner />}>
      <div>
        <OtherComponent />
      </div>
    </React.Suspense>
  );
}

React v.16.6よりサポートされているので、すでにお馴染みのユースケースかもしれません。

上記の例では、React.lazyを使った遅延コンポーネント<OtherComponent>のローディングの待機中に、フォールバック用のコンテンツ<Spinner>を表示します。

React.lazyコード分割に使われる関数ですが、Suspenseコンポーネント内で遅延コンポーネントをレンダーすることによって、UIが一貫して表示されます。
コード分割 – React

データフェッチングでのSuspense

現時点で公式がサポートしているユースケースはReact.lazyを使ったもののみとなりますが、
ReactチームがSuspenseで描くヴィジョンはより大きなもののようです。

サスペンスはクライアントで React.lazy を使ってコードを分割する際にも利用できます。しかし我々がサスペンスを使って実現したいと構想しているのは、コードのロードよりもずっと多くのことです。目標は、サスペンスのサポートを拡張していき、いずれはサスペンスによるひとつの宣言的なフォールバックが、あらゆる非同期的な操作(コード、データ、画像などのロード)を扱えるようにすることです。
React v18.0 – React Blogより引用

その一つがデータフェッチングでのSuspenseの利用です。

従来のやり方

Dogs.jsx
function Dogs() {
  const { loading, error, data } = useQuery(GET_DOGS);

  if (loading) return 'Loading...';
  if (error) return `Error! ${error.message}`;

  return (
    <ul>
      {data.dogs.map((dog) => (
        <li key={dog.id}>{dog.breed}</li>
      ))}
    </ul>
  );
}

非同期ローディングを行うコンポーネントにおいて"ローディング中の処理"と"ローディング完了時の処理"はまとまっています。

Suspenseなやり方

Dogs.jsx
function Dogs() {
  const { data } = useQuery(GET_DOGS);

  return (
    <ul>
      {data.dogs.map((dog) => (
        <li key={dog.id}>{dog.breed}</li>
      ))}
    </ul>
  );
};
App.jsx
function App() {
  return (
    <React.Suspense fallback={<Spinner />}>
      <Dogs />
    </React.Suspense>
  );
};

従来のif (isLoading)のような手続き的なプログラムだったところが、ロード中という状態の処理がより宣言的な記述になっています。これにより、データの読み込みを担当するコンポーネントの責務が簡略化されています。

上記はアイディアをコード例として示しましたが、実際に使い始めたいという人は、React 18では、Relay、Next.js、Hydrogen、Remixなどのフレームワークを使えば、データフェッチにSuspenseを使い始めることができるそうです。(※技術的には可能という意味で一般的な戦略としてはまだ推奨されていない)
将来的には、フレームワークを使わずにSuspenseで簡単にデータにアクセスする新たに基本機能を提供するかもしれないそうなので、今後のアップデートが期待です。
参照:データフレームワークにおけるサスペンス

その他のユースケース

その他のユースケースとして参照リンクを紹介するのみとなりますが、以下のようなものがあります。
サーバーサイドコンポーネント+Suspenseはかなり個人的にもわくわくする機能です。

まとめ

Concurrent Reactはより良いユーザ体験を実現するものだけでなく、開発者としてもConcurrent Reactで利用できるようになる機能に合わせた設計をしていく必要があると感じました。
Concurrent Reactによる新しい機能も、React ecosystemのConcurrentサポート対応も、今後どんどんアップデートされていくと思うので、今後もConcurrent Reactの動きから目が離せません。

参照リンク

This article is also available in English: https://dev.to/takuyakikuchi/a-high-level-overview-of-concurrent-react-1iim

GitHubで編集を提案

Discussion