始めよう
このガイドでは 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 パラメーターを次のセクションで見ていきましょう。