準備
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
一番シンプルなルーティング
最初に単純にページ(コンポーネント)の表示を切り替えるだけのルーティング設定をしてみます。
表示されるページ、ホームページと記事一覧ページコンポーネントを作ります。
src/pages/Home.tsx
import React from 'react' const Home: React.VFC = () => { return ( <div> <h1>ホーム</h1> </div> ) } export default Home
src/pages/Posts.tsx
import React from 'react' const Posts: React.VFC = () => { return ( <div> <h1>記事一覧</h1> </div> ) } export default Posts
この二つのページを切り替えられるようにルーターの設定をします。
routes.tsx
を作成し次のようにします。
src/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
を次のように編集します。
src/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 App
Link
のto
にroutes
で設定したパスを指定することで、指定したコンポーネントに遷移します。
Outlet
にはパスにマッチしたコンポーネントが表示されます。
これで画面の切り替えができるようになりました。
下層ページのルート設定
次に記事一覧の下に記事詳細ページを作ってみます。
routes.tsx
から編集します。
src/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をそのまま表示してみます。
src/pages/Post.tsx
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 Post
IDはuseMatch().params
で取得することができます。
詳細ページの準備ができたので、一覧ページから遷移できるようにリンクを追加しましょう。
src/pages/Posts.tsx
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から設定します。
src/types/Post.tsx
export type PostType = { id: number title: string body: string }
APIの取得処理も切り出しておきましょう。
src/api/PostApi.ts
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は各ページでロードしたデータを使用するのに必要です。
src/api/PostApi.ts
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)) }) } ] } ]
各ページでロードしたデータを表示してみます。
src/pages/Posts.tsx
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 Posts
src/pages/Post.tsx
import 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を下記のように設定します。
src/App.tsx
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
を下記ようにすればページ毎にコードを分割して、アクセスがあったときに読み込むということができます。
src/routes.tsx
element: () => import('./pages/Posts').then((mod) =>),
React Locationにはその他にもいろいろな機能があります。
詳しくは下記ドキュメントをご確認ください。
Overview | React Location | TanStack