はじめに
実用的なSPAの開発プロジェクトでvue.jsを採用するならば,何かしらのルーティングライブラリを一緒に使うことになるかと思います。
今回,シンプルなルーティングライブラリであるpage.jsを試したみたところ,page.js側で受け取ったパラメータをコンポーネントに渡す方法に少し悩んだので,こんな書き方はどうでしょう?という意味でサンプルコードを紹介します。
前提コード
home
とexample
という2つのコンポーネントがあり,それぞれ/
というパスと/example/:q
というパスに対応しています。example
の方はパラメータq
を受け取り,パラメータに応じたコンテンツを表示します。
<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
を定義します。
var app = new Vue({
el: "#container",
data: {
main: undefined,
params: {}
}
})
page.jsのコールバックでルートViewModelのparams
に受け取ったパラメータを設定します。
page('/example/:q', function(ctx) {
app.params = ctx.params
app.main = "example"
})
テンプレート側からは次のようにparams
を参照可能です。
<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のコールバックを次のように書き換えます。
page('/example/:q', function(ctx) {
app.main = "example"
// 初回はまだexampleコンポーネントが生成されていないので
// 次のタイミングまで待つ
Vue.nextTick(function() {
app.$broadcast("example-init", ctx.params)
})
})
コンポーネント側では次のようにイベントを受け取ります。
Vue.component("example", {
template: "#example",
data: function() { return {
params: undefined
}},
ready: function() {
this.$on("example-init", function(params) {
this.params = params
// ここでAPIを叩いたり色々やる
})
}
})
テンプレート側からは次のようにparams
を参照可能です。
<script type="text/v-template" id="example">
<h1>example</h1>
<div>q: {{params.q}}</div>
<content />
</script>
直接コンポーネントのメソッドを呼ぶ
もしくは,v-ref
ディレクティブによりコンポーネントに名前を設定し,直接example
コンポーネントのメソッドを呼ぶ方法もあります。
HTML中のコンポーネントの部分にv-ref
ディレクティブを追加します。
<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のコールバックを次のように書き換えます。
page('/example/:q', function(ctx) {
app.main = "example"
// 初回はまだexampleコンポーネントが生成されていないので
// 次のタイミングまで待つ
Vue.nextTick(function() {
app.$.child.init(ctx.params)
})
})
コンポーネント側は次のようにします。
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>