vue.jsとpage.jsの組み合わせでルーティングをするときのサンプル

## はじめに

実用的なSPAの開発プロジェクトでvue.jsを採用するならば，何かしらのルーティングライブラリを一緒に使うことになるかと思います。

今回，シンプルなルーティングライブラリであるpage.jsを試したみたところ，page.js側で受け取ったパラメータをコンポーネントに渡す方法に少し悩んだので，こんな書き方はどうでしょう？という意味でサンプルコードを紹介します。

## 前提コード

`home`と`example`という2つのコンポーネントがあり，それぞれ`/`というパスと`/example/:q`というパスに対応しています。`example`の方はパラメータ`q`を受け取り，パラメータに応じたコンテンツを表示します。

```html
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/0.11.0/vue.min.js"></script>
<script src="//cdn.rawgit.com/visionmedia/page.js/master/page.js"></script>

<div id="container">
  <div v-component="{{main}}">
    <div><a href="/tmp/">home</a></div>
    <div><a href="/tmp/example/hoge">example/hoge</a></div>
    <div><a href="/tmp/example/fuga">example/fuga</a></div>
  </div>
</div>

<script type="text/v-template" id="home">
  <h1>home</h1>
  <content />
</script>
<script type="text/v-template" id="example">
  <h1>example</h1>
  <div>パラメータをここに表示する</div>
  <content />
</script>

<script>
  var app = new Vue({
    el: "#container",
    data: {
      main: undefined
    }
  })
  Vue.component("home", {
    template: "#home"
  })
  Vue.component("example", {
    template: "#example"
  })

  page('/', function(ctx) {
    app.main = "home"
  })
  page('/example/:q', function(ctx) {
    app.main = "example"
    // ctx.paramsをどうやってexampleコンポーネントに渡す？
  })
  page()
</script>
```

## $root経由でパラメータを渡す

まず，ルートViewModelのフィールドとして`params`を定義します。

```js
  var app = new Vue({
    el: "#container",
    data: {
      main: undefined,
      params: {}
    }
  })
```

page.jsのコールバックでルートViewModelの`params`に受け取ったパラメータを設定します。

```js
  page('/example/:q', function(ctx) {
    app.params = ctx.params
    app.main = "example"
  })
```

テンプレート側からは次のように`params`を参照可能です。

```html
<script type="text/v-template" id="example">
  <h1>example</h1>
  <div>q: {{$root.params.q}}</div>
  <content />
</script>
```

## 上記の方法の問題点

$root経由で単純にパラメータを渡す方法だと，受け取ったパラメータを使ってAPIを叩き，返ってきた結果をViewに表示するようなケースでうまく行きません。

具体的には，`/example/hoge`から`/example/fuga`に遷移するようなケースで，コンポーネントが持つ`ready`などのライフサイクルハンドラが呼ばれないため，APIを叩くコードを処理することができず，困ってしまうことになります。

そこで，この問題を解決する2つの方法を考えました。

## vue.jsのイベントシステムを使う

vue.jsにはコンポーネント間でイベントのハンドリングを行うイベントシステムが備わっているので，それを使ってみます。

page.jsのコールバックを次のように書き換えます。

```js
  page('/example/:q', function(ctx) {
    app.main = "example"

    // 初回はまだexampleコンポーネントが生成されていないので
    // 次のタイミングまで待つ
    Vue.nextTick(function() {
      app.$broadcast("example-init", ctx.params)
    })
  })
```

コンポーネント側では次のようにイベントを受け取ります。

```js
  Vue.component("example", {
    template: "#example",
    data: function() { return {
      params: undefined
    }},
    ready: function() {
      this.$on("example-init", function(params) {
        this.params = params

        // ここでAPIを叩いたり色々やる
      })
    }
  })
```

テンプレート側からは次のように`params`を参照可能です。

```html
<script type="text/v-template" id="example">
  <h1>example</h1>
  <div>q: {{params.q}}</div>
  <content />
</script>
```

## 直接コンポーネントのメソッドを呼ぶ

もしくは，`v-ref`ディレクティブによりコンポーネントに名前を設定し，直接`example`コンポーネントのメソッドを呼ぶ方法もあります。

HTML中のコンポーネントの部分に`v-ref`ディレクティブを追加します。

```html
<div id="container">
  <div v-component="{{main}}" v-ref="child">
    <div><a href="/tmp/">home</a></div>
    <div><a href="/tmp/example/hoge">example/hoge</a></div>
    <div><a href="/tmp/example/fuga">example/fuga</a></div>
  </div>
</div>
```

page.jsのコールバックを次のように書き換えます。

```js
  page('/example/:q', function(ctx) {
    app.main = "example"

    // 初回はまだexampleコンポーネントが生成されていないので
    // 次のタイミングまで待つ
    Vue.nextTick(function() {
      app.$.child.init(ctx.params)
    })
  })
```

コンポーネント側は次のようにします。

```js
  Vue.component("example", {
    template: "#example",
    data: function() { return {
      params: undefined
    }},
    methods: {
      init: function(params) {
        this.params = params

        // ここでAPIを叩いたり色々やる
      }
    }
  })
```

## おわりに

初心者なりにこんな方法はどうかな？ということで考えてみましたが，もっといいやり方があれば教えていただけると嬉しいです。

ちなみに，記事中には書きませんでしたが，`/`や`/example/*`へのアクセスはサーバー側で適宜リダイレクトしてあげる必要があります。Apacheならば次のような設定を使うことになるでしょう。

```
<ifModule mod_rewrite.c>
  RewriteEngine On
  RewriteCond %{REQUEST_FILENAME} !-f
  RewriteCond %{REQUEST_FILENAME} !-d
  RewriteCond %{REQUEST_URI} !index
  RewriteRule (.*) index.html [L]
</ifModule>
```

## 関連リンク

- [Event System / Component System - vue.js](http://vuejs.org/guide/components.html#Event_System)
- [Child Reference / Component System - vue.js](http://vuejs.org/guide/components.html#Child_Reference)