準備
TypeScript版のCreate React App を使用します。
下記でプロジェクトディレクトリを作成。
$ yarn create react-app react-location-test --template typescript$ cd react-location-test続いてライブラリのインストール。
「React Location」とAPI通信するために「Axios」も入れておきます。
$ yarn add @tanstack/react-location axiosバージョンはそれぞれこんな感じです。
typescript: 4.4.2
react: 17.0.2
@tanstack/react-location: 3.7.0
axios: 0.26.1
一番シンプルなルーティング
最初に単純にページ(コンポーネント)の表示を切り替えるだけのルーティング設定をしてみます。
表示されるページ、ホームページと記事一覧ページコンポーネントを作ります。
import React from 'react'
const Home: React.VFC = () => { return ( <div> <h1>ホーム</h1> </div> )}
export default Homeimport React from 'react'
const Posts: React.VFC = () => { return ( <div> <h1>記事一覧</h1> </div> )}
export default Postsこの二つのページを切り替えられるようにルーターの設定をします。
routes.tsxを作成し次のようにします。
import { Route, ReactLocation } from '@tanstack/react-location'import Home from './pages/Home'import Posts from './pages/Posts'
export const location = new ReactLocation()
export const routes: Route[] = [ { path: '/', element: <Home /> }, { path: '/posts', element: <Posts /> },]routes変数にルーターの設定をしています。
下記の挙動になることを想定しています。
| パス | コンポーネント |
|---|---|
| / | Homeコンポーネント |
| /posts | Postsコンポーネント |
App.tsxを次のように編集します。
import { Router, Outlet, Link } from '@tanstack/react-location'import { routes, location } from './routes'
const App = () => { return ( <div className="App"> <Router routes={routes} location={location}> <header> <ul> <li><Link to="/">ホーム</Link></li> <li><Link to="posts">記事一覧</Link></li> </ul> </header> <Outlet /> </Router> </div> )}
export default AppLinkのtoにroutesで設定したパスを指定することで、指定したコンポーネントに遷移します。
Outletにはパスにマッチしたコンポーネントが表示されます。
これで画面の切り替えができるようになりました。
下層ページのルート設定
次に記事一覧の下に記事詳細ページを作ってみます。
routes.tsxから編集します。
import { Route, ReactLocation } from '@tanstack/react-location'import Home from './pages/Home'import Posts from './pages/Posts'import Post from './pages/Post'
export const location = new ReactLocation()
export const routes: Route[] = [ { path: '/', element: <Home /> }, { path: '/posts', element: <Posts />, children: [ { path: ':id', element: <Post /> } ] }]下層ページを作りたい場合はchildrenの中に記述していきます。
記事詳細は「posts/1」「posts/2」とかIDによって動的に切り替えたいので、pathは:idにします。
次に記事詳細ページのPostコンポーネントを作ります。
ひとまず受け取ったIDをそのまま表示してみます。
import React from 'react'import { useMatch } from '@tanstack/react-location'
const Post: React.VFC = () => {
const { id } = useMatch().params
return ( <div> <h2>投稿詳細</h2> <p>記事{id}</p> </div> )}
export default PostIDはuseMatch().paramsで取得することができます。
詳細ページの準備ができたので、一覧ページから遷移できるようにリンクを追加しましょう。
import React from 'react'import { Link, Outlet } from '@tanstack/react-location'
const Posts: React.VFC = () => { return ( <div> <h1>記事一覧</h1> <ul> <li> <Link to="/posts/1">記事1</Link> <Link to="/posts/2">記事2</Link> <Link to="/posts/3">記事3</Link> </li> <Outlet /> </ul> </div> )}
export default Postsこれで各詳細ページへ遷移できるようになります。
LoaderでAPIのデータを取得
React Locationの特徴の一つにLoaderという機能があります。
これを使用することでページの表示前にAPIからデータの取得することができます。
APIには「JSONPlaceholder」を使用ます。
取得するAPIのTypeから設定します。
export type PostType = { id: number title: string body: string}APIの取得処理も切り出しておきましょう。
import axios from 'axios'import { PostType } from '../types/Post'
// 記事一覧取得const getPosts = async () => { return await axios .get<PostType[]>('https://jsonplaceholder.typicode.com/posts?_limit=5') .then(r => r.data)}
// 記事詳細取得const getPost = async (id: number) => { return await axios .get<PostType>(`https://jsonplaceholder.typicode.com/posts/${id}`) .then(r => r.data)}
export { getPosts, getPost}作成したAPIをルーターのLoaderに設定をします。
LocationGenericsは各ページでロードしたデータを使用するのに必要です。
import { Route, ReactLocation, MakeGenerics } from '@tanstack/react-location'import Home from './pages/Home'import Posts from './pages/Posts'import Post from './pages/Post'import type { PostType } from './types/Post'import { getPosts, getPost } from './api/PostApi'
export type LocationGenerics = MakeGenerics<{ LoaderData: { posts: PostType[] post: PostType }}>
export const location = new ReactLocation()
export const routes: Route[] = [ { path: '/', element: <Home /> },{ path: '/posts', element: <Posts />, loader: async () => ({ posts: await getPosts() }), children: [ { path: ':id', element: <Post />, loader: async ({params: {id}}) => ({ post: await getPost(Number(id)) }) } ] }]各ページでロードしたデータを表示してみます。
import React from 'react'import { Link, Outlet, useMatch } from '@tanstack/react-location'import { LocationGenerics } from '../routes'
const Posts: React.VFC = () => { const { posts } = useMatch<LocationGenerics>().data
return ( <div> <h1>記事一覧</h1> <ul> {posts?.map(post => ( <li key={post.id}> <Link to={post.id}>{post.title}</Link> </li> ))} </ul> <Outlet /> </div> )}
export default Postsimport React from 'react'import { useMatch, Link } from '@tanstack/react-location'import { LocationGenerics } from '../routes'
const Post: React.VFC = () => { const { post } = useMatch<LocationGenerics>().data
return ( <div> <h2>投稿詳細</h2> { post ? <div> <p>{post.title}</p> <div>{post.body}</div> </div> : <div>記事はありません</div> } <Link to="/posts">記事一覧に戻る</Link> </div> )}
export default Postローディング&エラー表示
初期設定での挙動はAPIの取得が完了した後、画面遷移されます。
APIの取得しているときはローディング中的な表示をしたいですね。
全体で同じ挙動にする場合はRouterを下記のように設定します。
const Loading = () => ( <div>ローディング...</div>)
const Error = () => ( <div>エラー</div>)
const App = () => { return ( <div className="App"> <Router routes={routes} location={location} defaultPendingMs={0} defaultPendingElement={<Loading />} defaultErrorElement={<Error />} > ... </Router> </div> )}defaultPendingMsを0にすることで、ローディング完了前にページ遷移をします。
defaultPendingElementにローディング中のコンポーネントを指定します。
APIエラー時の表示はdefaultErrorElementに設定したコンポーネントを表示できます。
このオプションはroutesのpendingMs、pendingElementオプションで各ページ単位で設定することもできます。
各ページの表示部分で細かく調節したい場合はuseMatchのisLoadingで制御することもできます。
const { data: { posts }, isLoading} = useMatch<LocationGenerics>()
if (isLoading) { return <div>ローディング</div>}ビルドファイルの分割
規模が大きくなるとWebpackなどでビルドしたファイルサイズが肥大化し初回の読み込み時間が長くなるということがあります。
ルートのelementを下記ようにすればページ毎にコードを分割して、アクセスがあったときに読み込むということができます。
element: () => import('./pages/Posts').then((mod) => ),React Locationにはその他にもいろいろな機能があります。
詳しくは下記ドキュメントをご確認ください。

