このページ内容は2021年1月21日以降、再調査・再検証してません。実際に扱う際は最新の情報にアクセスしてください。

link
React18

2022年1月2日現在、React18βがリリースされています。次はRC版でその2 ~ 4週間後にReact18がリリースされます。ここでは簡単にどういうものがReact18から使えるようになるのかについて触れていきます。

link
新しいルートAPIの追加

React18から新しいルートAPIが追加されます。ルートAPIはReactでアプリを作るために最初に宣言するAPIです。React17, 18ではそれぞれ以下のように宣言します

1
// React17 (レガシーモードと呼ばれるようになる)
2
const rootElement = document.getElementById("root");
3
ReactDOM.render(<App />, rootElement);
4
5
// React18
6
const rootElement = document.getElementById("root");
7
ReactDOM.createRoot(rootElement).render(<App />);

React18以降も以前までの宣言 (レガシーモード) を利用できますが、Automatic Batchingなどいくつかの機能が制限されます。Discussionのそこら辺の話が分かりやすくまとまっているので、気になる方は一読してみることをお勧めしますreact18

link
Automatic Batchingのアップデート

Automatic Batchingは連続して複数回のステート更新があるときに、複数回再レンダリングするのではなく、1度だけ再レンダリングする機能 (バッチ処理) です。React18以前はイベントハンドラ中にステート更新が連続した場合にのみ有効でしたが、React18の新しいルートAPIを使ってアプリを作る場合はPromiseやsetTimeoutなどが含まれる場合も再レンダリングが1度だけになります。

こちらのDiscussionreact18_automatic_batching_sampleで具体的なデモ付きで分かりやすく解説されているので、こちらを触ってみてください。簡単に説明すると、以下のようなコンポーネントがあるとします。ここのhandleClickの書き方によってレンダリングが1回で済む場合と2回実行される場合があります。

1
function App() {
2
const [count, setCount] = useState(0);
3
const [flag, setFlag] = useState(false);
4
5
/* handleClickの定義 */
6
7
return (
8
<div>
9
<button onClick={handleClick}>Next</button>
10
</div>
11
);
12
}

handleClickが以下のような定義 (handleClick1) のときは、2回ステート更新があってもレンダリングは1回だけですが、何かしらのコールバックイベントの後で実行される場合 (handleClick2) は2回レンダリングされます。

1
// 1度だけレンダリングされるケース
2
function handleClick1() {
3
setCount((c) => c + 1);
4
setFlag((f) => !f);
5
}
6
7
// 2回レンダリングされるケース
8
function handleClick2() {
9
fetchSomething().then(() => {
10
setCount((c) => c + 1);
11
setFlag((f) => !f);
12
});
13
}

React18からは新しいルートAPIを使うと、上記のようなコールバックイベント後にステート更新する場合も1度だけレンダリングするようになります。バッチ処理をしたくない場合はReactDOM.flushSync()を使うことで対応できます。

link
Suspenseコンポーネント

これはデータの受け取り状態を検知できるコンポーネントです。Suspenseコンポーネントの説明はReact Conf 2021の説明が分かりやすいですreact_2021_conf。日本語字幕に切り替えることができるので、時間のある方はこちらで確認することをオススメします。

ここではSuspenseを使わない場合の書き方から説明し、メリットやSuspenseの使い方を説明します。まず、適当なデータフェッチライブラリ (useDataFetch) があり、これを使うとデータ (item) と取得状況 (isLoading) が取れるとします。ここで、データが取得できるまではSpinnerコンポーネントを表示させ、データが取得できたらデータを表示させたい場合は以下のように書けます。

1
// Suspenseを使わない場合
2
function List({pageId}) {
3
const [items, isLoading] = useDataFetch(pageId)
4
5
if (isLoading) <Spinner /> // ロード状態のロジック
6
7
return items.map(item = > <li>{item}</li>);
8
}
9
10
// 省略
11
12
// レンダリング部分
13
<List pageId={pageId} />

これをSuspenseを使って書き直すと以下のようになります。ロード状態を処理するロジックが不要になるため、コードがスッキリし上から下に読むだけで何をしているか分かるようになります。

1
function List({pageId}) {
2
const [items] = useDataFetch(pageId)
3
4
return items.map(item = > <li>{item}</li>);
5
}
6
7
// 省略
8
9
// レンダリング部分
10
<Suspense fallback={<Spinner />}>
11
<List pageId={pageId} />
12
</Suspense>

このSuspenseはいくつも囲むことができるので、以下のような使い方もできます。ここでは、ヘッダーが表示されるまではSkeltonコンポーネントを、SpecialListが表示されるまではListPlaceHolderコンポーネントを表示させることができます。このように、ページの大枠は先に表示させつつ、データ取得に時間のかかるコンポーネントを後から表示させることができるようになります。

1
<Suspense fallback={<Skelton />}>
2
<Header />
3
<Suspense fallback={<ListPlaceHolder />}>
4
<ListLayout>
5
<SpecialList pageId={pageId} />
6
</ListLayout>
7
</Suspense>
8
</Suspense>

React18リリース時はRelayとの連携のみがサポートされますが、ApolloやSWR, React Queryなども後ほど連携できるようになるようですreact_suspense_library_support

link
React Server Components

これはSPAとSSRを共存させるための機能です。こちらはNext.jsの説明react18_server_componentが分かりやすいです。SPAで表示したいコンポーネント (クライアントコンポーネント) はxxx.client.jsで作成し、SSRで表示したいコンポーネント (サーバーコンポーネント) はyyy.server.jsで作成します。サーバーコンポーネント内でクライアントコンポーネントをインポートすることで、一部はSPAで表示させ、一部はSuspenseを使って後から表示させることができるようになります。

pages/home.server.js
1
import { Suspense } from 'react'
2
3
import Profile from '@components/profile.server.js'
4
import Content from '@components/content.client.js'
5
6
export default function Home() {
7
return (
8
<div>
9
<h1>Welcome to React Server Components</h1>
10
<Suspense fallback={'Loading...'}>
11
<Profile />
12
</Suspense>
13
<Content />
14
</div>
15
)
16
}

制約もいくつかあり、クライアントコンポーネントでサーバーコンポーネントをインポートできません。また、サーバーコンポーネントでuseStateやuseEffectなどのステート更新もできません。

React18には他にもstartTransition API (useTransitionの簡易版start_transition) や useSyncExternalStore API (旧useMutableSource APIuse_sync_external_store) などが追加されています。それぞれDiscussionで詳しく解説されているので、気になる方は読んでみてください。


自己紹介
はじめまして Pilefortです。
東京でエソジニアをしてます。
興味のあるスタックは、JavaScript (React, Vue), TypeScript, Rust, WebAssembly, AWS, Pulumi, Serverless Frameworkです。
このブログでは、普段の業務や趣味で気になったことをまとめたり、フロントやAWS, GitHubやTwitterで見かけた面白い記事やニュースをまとめるためのものです。少しでも何かの役に立てば幸いです。
サイトマップ
Notes
業務や趣味での気づき・メモ
Snippets
記事にするまでもないけど、便利なコマンドや豆知識
Works
同人誌一覧