React の軽量なルーティングライブラリ「Navi」を試してみる

We Are JavaScripters!【執筆初心者歓迎】 Advent Calendar 2018 の12日目の記事です。大遅刻してしまいました。 WeJSは聞く側で参加したことはあるのですが登壇したことはまだ無いので来年はLT枠で出場したいです。

Navi について

github.com

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として定義されたオブジェクトを非同期的に読み込んでルーティングを実現する仕組みになっています。

ルーティングの流れ

  1. <NavLink>オブジェクトに登録されたリンクをクリックする
  2. リンクに遷移すると同時に空白のページをレンダリング
  3. pages/index.jsで定義されたルーティングと対応するPromiseコンポーネントを読み込み、ローディングが完了するまで待機。
  4. Promiseが解決(ローディングが完了)されたらコンテンツとしてレンダリング

Navigationオブジェクトの作成

Navi による強力なルーティングの仕組みを利用するためには、src直下のindex.jsNavigationという 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 />を記述することでルーティングに紐づいた各コンポーネントを非同期的にレンダリングしてくれます。

routing
Navi

その他

その他、ローディングインジケータを表示したり存在しないページへのアクセスのハンドリングといった機能もありますが今回の記事ではその説明は省きます。

自分なりに英語のドキュメントを解釈しながらここまで説明してきましたが、より正確に理解するには公式のドキュメントを読みながら各自で動かしてみてください。 また公式チュートリアル動画が作られたようなので、より深くNaviを知りたい方はこちらを試聴してみてください。

www.youtube.com