はじめに
こんにちは、CX事業本部MAD事業部の森茂です。
React Routerがv6.4で再び大きく変化するようです。3月末に下記Remix公式ブログで事前の案内はありましたがReact Routerと同じチームが開発しているRemixの思想がReact Routerに取り込まれ、そのPre-release版とData APIについてのドキュメントが先日先行して公開されました。
Release Remixing React Router · remix-run/react-router
React Routerの安定版は2022年6月現在 v6.3.0となります。
React Router v6.4 Pre-release
React Routerがバージョンアップと聞くと身構える方も多いかもしれません:)
今回は機能(主にData API関連)の追加ということのようで、従来のReact Routerに(v6自体がすでに大きく変わっていますが...)、Loader Function
やAction Function
、Form
コンポーネントといったRemixならではの抽象化された機能が多数追加されています。6-3->6.4という進化からは想像できない範囲の機能が追加されルーティングライブラリというよりはRemixのCSR版フレームワークと言えるくらいの変革になるかもしれません。
ためしてみた
公式のデモもありますが、いったん手元でもCRA
を利用して動作させてみました。
$ yarn create react-app react-router-pre --template typescript
$ cd react-router-pre
$ yarn add react-router-dom@next
ルーティング用のコンポーネント
v6.4で追加されたData Loaderを利用するにはRoutes
ではなくDataBrowserRouter
の中にRoute
を展開するようです。Remixの特徴でもある並列のデータフェッチがv6.4でも利用できるようになったので、親子関係のコンポーネントともに用意してみます。
React RouterではRemixのLoader Function
を再現するために、ページコンポーネントとともにLoader
もインポートし、自身のコンポーネントへRoute
を経由して渡すようです。
src/App.tsx
import './App.css';
import { DataBrowserRouter, Route } from 'react-router-dom';
import { Root, loader } from './routes/root';
import { TodoList, loader as todoListLoader } from './routes/todoList';
function App() {
return (
// fallbackElementでデータフェッチ完了までの表示をフォールバックする
<DataBrowserRouter fallbackElement={<div>Loading...</div>}>
// 自身からexportしているloaderをコンポーネントに渡す
<Route path="/" element={<Root />} loader={loader}>
<Route
path="/todoList"
element={<TodoList />}
loader={todoListLoader}
/>
</Route>
</DataBrowserRouter>
);
}
export default App;
ルートページ(兼レイアウト)
/
で表示されるルートページを用意します。Outlet
を利用して/todoList
でroot.tsx
をレイアウトとしても利用し親子関係を用意します。Remixを利用されている方にはおなじみのjson
ヘルパーもあるようです。いったんエクスポートしたloader
をRoute
を経由してコンポーネント側のuseLoaderData
で受け取っています。
src/routes/root.tsx
import { json, LoaderFunction, Outlet, useLoaderData } from 'react-router';
import { Link } from 'react-router-dom';
type Todo = {
userId: number;
id: number;
title: string;
completed: boolean;
};
export const loader: LoaderFunction = async () => {
const response = await fetch(
'https://jsonplaceholder.typicode.com/todos?_limit=5',
).then(async (response) => {
return response.json();
});
return json<Todo>(response);
};
export const Root = () => {
// Routeを経由してloaderの中身を受け取る
const data = useLoaderData() as Todo[];
return (
<div>
<h1>Root Component</h1>
<div>
<Link to="todoList">Link</Link>
</div>
<ul>
{data.map((todo) => (
<li key={todo.id}>{todo.title}</li>
))}
</ul>
<hr />
<Outlet />
</div>
);
};
子ページ(/todoList
)
root.tsx
をレイアウトとして持つtodoList
ページを用意します。
src/routes/todoList.tsx
import { json, LoaderFunction, useLoaderData } from 'react-router';
type Todo = {
userId: number;
id: number;
title: string;
completed: boolean;
};
export const loader: LoaderFunction = async () => {
const response = await fetch(
'https://jsonplaceholder.typicode.com/todos?_limit=3',
).then(async (response) => {
return response.json();
});
return json<Todo>(response);
};
export const TodoList = () => {
const data = useLoaderData() as Todo[];
return (
<div>
<h1>todoList</h1>
<ul>
{data.map((todo) => (
<li key={todo.id}>{todo.title}</li>
))}
</ul>
</div>
);
};
ここまで用意したところで開発サーバーを起動します。
$ yarn start
これで、http:localhost:3000/
でroot.tsx
が、http://localhost:3000/todoList
でroot.tsx
をレイアウトとして利用しているtodoList.tsx
が表示されます。
開発者ツールで通信速度をFast 3G
にしてためしてみるとDataBrowserRouter
のfallbackElement
で用意したloading...
の文字が確認できると思います。内部的にはSuspenseを利用して実現しているようです。
またデータ取得に関しても/
でアクセスしてLinkボタンから/todoList
へ遷移した場合はそれぞれ1回のデータ取得を行い、直接/todoList
を開いた場合は並列にデータ取得を行いページの表示速度を早めているのがわかります。
レイアウトルート
個人的に注目している活用方法としては、Route
にpath
を指定しない形でpath
を持ったRoute
を囲むことでレイアウトのみの用途として利用する使い方です。RemixだとPassless layout routeという機能で実現したりします。ちょうど先日発表されたNext.jsのRFCでもlayout.js
として話題にあがっていたものと似たようなものです。
path
なしのRoute
コンポーネントでもloader
やaction
が利用できるのでRoute
の使い方の幅はかなり広くなりますが、それなりに複雑にもなりそうです:)
src/App.tsx
function App() {
return (
// pathを渡さずにRouteを囲むことでレイアウトのみの用途としても動作する
<DataBrowserRouter fallbackElement={<div>Loading...</div>}>
<Route element={<Root />} loader={loader}>
<Route path="/" element={<TodoList />} loader={todoListLoader} />
</Route>
</DataBrowserRouter>
);
}
もちろんこの形でもそれぞれのloader
のデータ取得は並列に行われます。
ところで
ルーティングライブラリでデータ取得も、ということであれば、React Routerとは別にReact Queryの作者でもあるTanner Linsley氏が開発しているReact Locationというルーティングライブラリも昨年11月にリリースされています。
こちらも遷移時にデータの取得を並列で行い、ドキュメント上ではParalizeという言葉を利用されていますが、データの完了を待ってコンポーネントを表示するという手法をとっているようです。React Routerとの大きな違いとしてはこのデータ取得した値をキャッシュとして保持できる点になるでしょう。React Query同様に開発者ツールがあるのでキャッシュの値を確認しながら開発できる点もメリットとなりそうです。
さいごに
とても簡単にではありますがReact Router v6.4 Pre-release版をためしてみました。Remixを利用しているユーザーからするとLoader
だけでなくAction
やForm
、useFetcher
などよく利用するものがそのまま同じような使い方で利用できるので、違和感なく導入できるかもしれません。
v6.4の公式ドキュメント(絶賛拡張中)や公式のデモでも他にどのような追加があるかぜひ確認してみてください。
ただ変化は大きく、もうすでひとつのルーティングライブラリではなくRemix
のCSR版フレームワークという位置づけの方が正しいかもしれません。これはこれで新たに物議を醸すバージョンアップとなりそうです。