このページ内容は2021年1月21日以降、再調査・再検証してません。実際に扱う際は最新の情報にアクセスしてください。
2022年1月2日現在、React18βがリリースされています。次はRC版でその2 ~ 4週間後にReact18がリリースされます。ここでは簡単にどういうものがReact18から使えるようになるのかについて触れていきます。
React18から新しいルートAPIが追加されます。ルートAPIはReactでアプリを作るために最初に宣言するAPIです。React17, 18ではそれぞれ以下のように宣言します
1// React17 (レガシーモードと呼ばれるようになる)2const rootElement = document.getElementById("root");3ReactDOM.render(<App />, rootElement);45// React186const rootElement = document.getElementById("root");7ReactDOM.createRoot(rootElement).render(<App />);
React18以降も以前までの宣言 (レガシーモード) を利用できますが、Automatic Batchingなどいくつかの機能が制限されます。Discussionのそこら辺の話が分かりやすくまとまっているので、気になる方は一読してみることをお勧めしますreact18。
Automatic Batchingは連続して複数回のステート更新があるときに、複数回再レンダリングするのではなく、1度だけ再レンダリングする機能 (バッチ処理) です。React18以前はイベントハンドラ中にステート更新が連続した場合にのみ有効でしたが、React18の新しいルートAPIを使ってアプリを作る場合はPromiseやsetTimeoutなどが含まれる場合も再レンダリングが1度だけになります。
こちらのDiscussionreact18_automatic_batching_sampleで具体的なデモ付きで分かりやすく解説されているので、こちらを触ってみてください。簡単に説明すると、以下のようなコンポーネントがあるとします。ここのhandleClickの書き方によってレンダリングが1回で済む場合と2回実行される場合があります。
1function App() {2const [count, setCount] = useState(0);3const [flag, setFlag] = useState(false);45/* handleClickの定義 */67return (8<div>9<button onClick={handleClick}>Next</button>10</div>11);12}
handleClickが以下のような定義 (handleClick1) のときは、2回ステート更新があってもレンダリングは1回だけですが、何かしらのコールバックイベントの後で実行される場合 (handleClick2) は2回レンダリングされます。
1// 1度だけレンダリングされるケース2function handleClick1() {3setCount((c) => c + 1);4setFlag((f) => !f);5}67// 2回レンダリングされるケース8function handleClick2() {9fetchSomething().then(() => {10setCount((c) => c + 1);11setFlag((f) => !f);12});13}
React18からは新しいルートAPIを使うと、上記のようなコールバックイベント後にステート更新する場合も1度だけレンダリングするようになります。バッチ処理をしたくない場合はReactDOM.flushSync()を使うことで対応できます。
これはデータの受け取り状態を検知できるコンポーネントです。Suspenseコンポーネントの説明はReact Conf 2021の説明が分かりやすいですreact_2021_conf。日本語字幕に切り替えることができるので、時間のある方はこちらで確認することをオススメします。
ここではSuspenseを使わない場合の書き方から説明し、メリットやSuspenseの使い方を説明します。まず、適当なデータフェッチライブラリ (useDataFetch) があり、これを使うとデータ (item) と取得状況 (isLoading) が取れるとします。ここで、データが取得できるまではSpinnerコンポーネントを表示させ、データが取得できたらデータを表示させたい場合は以下のように書けます。
1// Suspenseを使わない場合2function List({pageId}) {3const [items, isLoading] = useDataFetch(pageId)45if (isLoading) <Spinner /> // ロード状態のロジック67return items.map(item = > <li>{item}</li>);8}910// 省略1112// レンダリング部分13<List pageId={pageId} />
これをSuspenseを使って書き直すと以下のようになります。ロード状態を処理するロジックが不要になるため、コードがスッキリし上から下に読むだけで何をしているか分かるようになります。
1function List({pageId}) {2const [items] = useDataFetch(pageId)34return items.map(item = > <li>{item}</li>);5}67// 省略89// レンダリング部分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。
これはSPAとSSRを共存させるための機能です。こちらはNext.jsの説明react18_server_componentが分かりやすいです。SPAで表示したいコンポーネント (クライアントコンポーネント) はxxx.client.jsで作成し、SSRで表示したいコンポーネント (サーバーコンポーネント) はyyy.server.jsで作成します。サーバーコンポーネント内でクライアントコンポーネントをインポートすることで、一部はSPAで表示させ、一部はSuspenseを使って後から表示させることができるようになります。
pages/home.server.js1import { Suspense } from 'react'23import Profile from '@components/profile.server.js'4import Content from '@components/content.client.js'56export default function Home() {7return (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で詳しく解説されているので、気になる方は読んでみてください。