React の軽量なルーティングライブラリ「Navi」を試してみる
We Are JavaScripters!【執筆初心者歓迎】 Advent Calendar 2018 の12日目の記事です。大遅刻してしまいました。 WeJSは聞く側で参加したことはあるのですが登壇したことはまだ無いので来年はLT枠で出場したいです。
Navi について
Navi は React で使える軽量なルーターで、SEOが必要な大規模な静的サイトの構築に適したライブラリとして開発されています。作者は東京在住の James K. Nelson さん。先日の東京Node学園祭でご本人の登壇を聞いて存在を知りました。React のルーターは React-Router が有名ですが、使っていてちょっとつらみがあったので他の手段があるならぜひ試してみたいな、と思い GitHub の Readme を読みながら試してみました。
Getting Started
Create React App を使ってプロジェクトの雛形を作成してからNaviを導入してみます。Jamesさん本人も Create React App との組み合わせが強力、と押しているので早速それにならってチュートリアルを読みながらデモを動かしてみましょう。
インストール
create-react-app navi-demo cd navi-demo
Navi のパッケージは npm に登録された react-navi
navi
の二つからなるので、両方インストールします。
npm i --save react-navi navi
インストールが完了したらsrc
直下にpage
というディレクトリを作成します。
cd src && mkdir page
ルーティングの作成
次に、page/index.js
を作成します。ここにサイトマップとそのルーティングを以下のように書きます。
ルーティングにはcreateSwitch
を、ページコンテンツの生成にはcreatePage
というAPIがそれぞれ用いられます。
createSwitch
の中に paths という名前のオブジェクトを生成します。paths オブジェクトはネストしており、もう一段階下にサイトのURLを相対パスをキーとして登録し、中身にcreatePage
というコンポーネントを登録します。
// pages/index.js import { createPage, createSwitch } from 'navi' import * as React from 'react' import { NavLink } from 'react-navi' export default createSwitch({ paths: { '/': createPage({ title: "Navi", content: <div> <h2>Navi</h2> <p>A router/loader for React</p> <nav><NavLink href='/hello'>Hello</NavLink></nav> </div> }), '/hello': createPage({ title: "Hello", getContent: () => import('./hello.js') }), } })
createPage
の中身はtitle (ブラウザに表示するページタイトル)とcontent に分かれます。contentについてはJSXをベタ書きしたり、getContent関数で外部ファイルを読み込んだり、JSONを記述することもできます。上記の例ではサイトのindex('/'
)のcontent
はJSXがベタ書きされていて、hello
というページへのルーティングの中は外部ファイルをimportしていますね。
それでは同階層にある hello.js の中身をみてみましょう。
// pages/hello.js import * as React from 'react' import { NavLink } from 'react-navi' export default function Hello() { return( <div> <h2>Hello</h2> <p>This is demo.</p> </div> ) }
一見するとごく普通の React の Functional Component が読み込まれているように見えますがここに Navi のミソがあります。
pages/index.js
では<NavLink>
というオブジェクトを介して/reference
へのリンクが貼られています。
Naviでは、<NavLink>
へ登録されたリンクへジャンプすると一度中身が空のページをレンダリングし、そこへPromise
として定義されたオブジェクトを非同期的に読み込んでルーティングを実現する仕組みになっています。
ルーティングの流れ
<NavLink>
オブジェクトに登録されたリンクをクリックする- リンクに遷移すると同時に空白のページをレンダリング
pages/index.js
で定義されたルーティングと対応するPromiseコンポーネントを読み込み、ローディングが完了するまで待機。- Promiseが解決(ローディングが完了)されたらコンテンツとしてレンダリング
Navigation
オブジェクトの作成
Navi による強力なルーティングの仕組みを利用するためには、src
直下のindex.js
にNavigation
という Navi の根幹をなすオブジェクトを作成する必要があります。Navigation
オブジェクトはcreateBrowserNavigation()
という API を用いて以下のように作成します。
// index.js import * as React from 'react' import * as ReactDOM from 'react-dom' import { createBrowserNavigation } from 'navi' import pages from './pages' import App from './App' async function main() { let navigation = createBrowserNavigation({ pages }) // Wait until async content is ready, or has failed. await navigation.steady() ReactDOM.render( <App navigation={navigation} />, document.getElementById('root') ); } // Start the app main()
main()
関数をasync
で定義し、その中でcreateBrowserNavigation()
を./pages
直下のコンポーネントとをまるごとラップする形で呼び出します。navigation
に内包されたPromise
オブジェクトが解決されるとsteady()
が実行されて非同期的に各ルーティングのコンテンツをレンダリングする仕組み、という解釈でいいのかな。
Appを定義する
これまでに定義した各種コンポーネントを<App>
内に仕込めば静的サイトの構築がいよいよ完了です。ここで新たに、<NavProvider>
と<NavRoute>
というコンポーネントが登場します。これらの仕組みは React の Context APIに近いっぽい?
<NavProvider>
が Context API の Provider で、<NavRoute>
が Consumer という風に理解しています。
// App.js import * as React from 'react' import { NavLink, NavProvider, NavRoute } from 'react-navi' import './App.css' class App extends React.Component { render() { return ( <NavProvider navigation={this.props.navigation}> <div className="App"> <header className="App-header"> <h1 className="App-title"> <NavLink href='/'>Navi</NavLink> </h1> </header> <NavRoute /> </div> </NavProvider> ); } }
src/index.js
で<App>
に Navigation
オブジェクトを props として渡していますね。Consumer にあたる<NavRoute />
を記述することでルーティングに紐づいた各コンポーネントを非同期的にレンダリングしてくれます。
その他
その他、ローディングインジケータを表示したり存在しないページへのアクセスのハンドリングといった機能もありますが今回の記事ではその説明は省きます。
自分なりに英語のドキュメントを解釈しながらここまで説明してきましたが、より正確に理解するには公式のドキュメントを読みながら各自で動かしてみてください。 また公式チュートリアル動画が作られたようなので、より深くNaviを知りたい方はこちらを試聴してみてください。