始めよう
このガイドでは create-react-app を用いて作成したまっさらなプロジェクトに、Navi を使ってルーティング機能を追加していきます。
基礎となるコンポーネント
大抵の Navi を使用するアプリケーションにおいては、Navi の <Router> コンポーネントが一番上の階層に配置されます。このコンポーネントが、宣言的で非同期なルーティング機能を React アプリケーションに追加する役割を担います。手始めに <Router> コンポーネントを create-react-app によって生成された index.js ファイルの中でレンダーしていきましょう。
<Router> コンポーネントで用いる routes は Navi の mount(), route() や matcher 関数を用いて次のように宣言します。
import { lazy, mount, route } from 'navi'
import { Router } from 'react-navi'
// routes を定義する
const routes =
mount({ '/': route({ title: 'My Shop',
getData: () => api.fetchProducts(),
view: <ShopLandingPage />,
}),
'/products': lazy(() => import('./productsRoutes')), })
// 定義した routes を `<Router>` コンポーネントに渡す
<Router routes={routes}> ...
</Router>これで <Router> ができました。/ へはお店のランディングページがひもづけられ、/products には遅延読み込みされるページがひもづけられています。
では次のステップに進みましょう。現在の route のための view 要素をどこに描画するのか、ということを決めます。そのためには <View /> コンポーネントを <Router> の中でどこでもいいので配置しましょう。これはどこでも配置できます。例えば <Link> を含んだヘッダー部分を与える <Layout> コンポーネント中に配置することもできます。
import { View } from 'react-navi'
ReactDOM.render(
<Router routes={routes}>
<Layout>
<View /> </Layout>
</Router>,
document.getElementById('root')
)どうですか?非常にシンプルですよね。けれどちょっと気になるところがありますね… もし遅延読み込みされる /products に移動したら、一体何が表示されるのでしょうか? この部分は import() を用いて読み込みされるので、Promise オブジェクトを返します。つまり最初は描画するものが何もありません。ですが幸いなことに React の新機能である <Suspense> を使うことで、宣言的に promise が解決されるのを待つことができます。つまり <View> を <Suspense> でラップしてあげれば、you’re off and racing!
Routing Hooks
view で使用するデータを取得するための getData() 関数を route の中で定義したのを覚えていますか?
route({
title: 'My Shop',
getData: () => api.fetch('/products'),
view: <Landing />,
})一体これによって取得したデータにアクセするにはどうしたらいいのでしょうか? そのためには React の Hooks を使用します!
Navi’s useCurrentRoute() hook は <Router> タグの内側に存在するファンクショナルコンポーネント内であればどこでも実行することができます。useCurrentRoute() hook は Route オブジェクトを返します。これには Navi が現在の URL について知りうる全ての情報が含まれています。この情報の中に、getData() で取得した情報も含まれています。
今の所うまくいっていますね。ただ /products をクリックした場合のことを想像してみてください。このリンク先は動的に読み込まれるのでした。フェッチするのには時間がかかりますので、その間に何も表示されません。何かを表示したほうがいいですよね。
最初の選択肢は <Suspense> を使って、ローディング中に fallback 用の要素を表示する方法です。ただしこの方法の問題点は、クリックした瞬間に目的のルートへすぐに移動してしまい、何も情報が見えなくなってしまうことです。

本当に やりたいのは多分、次のルートが読み込まれるまでの間、現在のページでロードバーを表示することでしょう。しかもロードに 100ms しかかからない場合には表示したくない。つまりロードしてから 100ms までの間はインディケーターを表示せず、100ms からロードが終わるまでの間は現在のページにインディケーターを表示させる、という挙動です。たった 100ms の時間のためにインディケーターを表示するのはあまりかっこよくないからです。

どうすればロードインディケーターを、ロードがすぐに終わらない時にだけ表示できるでしょうか?通常これは実装が非常に難しいのですが、Navi であればシンプルに実現できます。useLoadingRoute() hook を使うだけです。
これがどのようになされているか説明しますね。useCurrentRoute() は直近の ロードが完了した ルートを返します。そして useLoadingRoute() のほうは「リクエストはされたが、まだロードが完了していない」ルートを返します。ユーザーがリンクをクリックするまでは、useLoadingRoute() は undefined を返します。どうです。シンプルでしょう?
Error Boundaries
非同期な data と view につきものなのは、ロードが失敗することです。幸運にも、React は エラーが投げられた場合にそれをうまく処理するツールを提供してくれています。そう、Error Boundaries です。
<Suspense> タグで <View /> をラップした部分を振り返ってみましょう。 <View /> がまだロードされていない route に遭遇すると、<View /> は promise を投げます。それによって React にある一定の期間、フォールバック用の要素を表示させておく ことができます。<Suspense> が promise をキャッチしてようなものだと考えていいでしょう。Promise が解決されると、子要素は再レンダリングされます。
同様に <View /> が getView() もしくは getData() が失敗したのを見つけると、<View /> はエラーを投げます。例えば router が 404-not-found に遭遇した場合には、<View /> もエラーを投げています。これらのエラーは Error Boundary コンポーネントでキャッチすることができます。大抵の場合は自分自身で error boundaries を作る必要がありますが、Navi には <NotFoundBoundary> コンポーネントがあります。これを使うことで Not Found errors をキャッチして、フォールバック用のメッセージを表示することができます。
次に扱う内容
さて Navi に取り組んできました。その過程で Navi のコンポーネントや、いくつかの matcher 関数 を使いました。しかし、コンポーネントの実装内部を見ていただくとわかるのですが、navi 自体は純粋な JavaScript で書かれています。実は Navi は二つのパッケージに分かれているのです。
naviはコアとなるルーティングの実装部分で、ピュア JavaScript で書かれています。react-naviは navi のコア部分の薄いラッパーで、React のために設計されています。
Navi が持つすべての可能性を理解するためには、Navi それ自体のコアコンセプトを理解したいはずです。つまり requests や routes や matchers といった要素です。実はすでにこれらの概念はこのページで扱いました。そしてさらにこれらの概念を、説明していきます。まずは URL パラメーターを次のセクションで見ていきましょう。
