2016/12/08

vuex-router-syncを使ってgettersやactions,mutations内からroute情報を取得する

Vuexとvue-routerを共存させて、gettersやactions、mutations内でrouterの情報(パスやクエリパラメータなど)を使いたい場合がある。しかし、実現するにはViewModelからdispatchcommitの引数にthis.$routeを渡さないとできない。gettersの場合は、そもそもパラメータを受け取ることができないので、route情報を使うことはできない。(グローバル変数でrouteを持つというバッドプラクティスもあるけど…。)

そんなときに便利なのが「vuex-router-sync」というライブラリ。
これを使うとstore.state.routeでroute情報にアクセスすることができる。

ということで、vuex-router-syncを使ったサンプルコードを書いてみた。
記事の最後に、vuex-router-syncの詳細仕様をまとめておく。

開発ディレクトリの作成


これから紹介するサンプルコードはvue-cliを使って開発ディレクトリを作っている。ディレクトリ構成と違う場合は、適宜読み替えてほしい。vue-cliの使い方については、以下の記事を参照。

.
├── index.html
└── src
     ├── App.vue
     ├── main.js
     ├── pages
     │   ├── Detail.vue
     │   └── Index.vue
     ├── router    // <-- vue-routerの定義
     │   └── index.js
     └── store     // <-- Vuexの定義
         └── index.js



vuex-router-syncをインストールする


Vuexとvue-routerのバージョンが2.0以上の場合は、「@next」を付けて最新版を取得する。
# Vuexとvue-routerをインストール
$ npm i -S vuex vue-router

# vuex-router-syncの最新バージョンをインストール
$ npm i -S vuex-router-sync@next



vuex-router-syncを使う


はじめに、vue-routerの定義を「./router/index.js」に書く。
// ./router/index.js
import Vue from 'vue'
import VueRouter from 'vue-router'

Vue.use(VueRouter)

const router = new VueRouter({
  mode: 'history',
  base: __dirname,
  routes: [
    {
      path: '/',
      name: 'index',
      component: require('../pages/Index.vue')
    },
    {
      path: '/detail/:id',
      name: 'detail',
      component: require('../pages/Detail.vue')
    }
  ]
})

export default router

今回は名前付きルートで定義しているが、nameはあってもなくても良い。



次に、Vuexの定義を「./store/index.js」に書く。
// ./store/index.js
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

const state = {
  list: [
    { id: 1, title: 'title-1' },
    { id: 2, title: 'title-2' },
    { id: 3, title: 'title-3' },
    { id: 4, title: 'title-4' },
    { id: 5, title: 'title-5' }
  ]
}

const actions = {
  done ({ state, commit }) {
    // state.routeでアクセス可能
    console.log('actions:', state.route.params.id)
    commit('done')
  }
}

const mutations = {
  done (state) {
    // state.routeでアクセス可能
    console.log('mutations:', state.route.params.id)
  }
}

const getters = {
  list: state => state.list,
  detail: state => {
    // state.routeでアクセス可能
    return state.list.find(l => l.id.toString() === state.route.params.id.toString()) || {}
  }
}

export default new Vuex.Store({
  state,
  actions,
  mutations,
  getters
})

vuex-router-syncを使うことでstate.route.path, state.route.params, state.route.queryにアクセスできる。(通常はできない)



次に、メイン処理を「./main.js」に書く。
// ./main.js
import Vue from 'vue'
import App from './App'

import { sync } from 'vuex-router-sync'
import router from './router'
import store from './store'

sync(store, router)

new Vue({
  router,
  store,
  render: h => h(App)
}).$mount('#app')

sync(store, route)でvuex-router-syncを使えるようになる。



最後に、Vueファイルを書く。

まずはメインとなる「App.vue」
このファイルを中心にページやコンポーネントを読み込むことになる。
<!-- ./App.vue -->
<template>
  <div id="app">
    <router-view></router-view>
  </div>
</template>


次に「Index.vue」。これはstore.state.listを一覧表示している。
リンクをクリックすることで「/detail/:id」のページに移動する。
<!-- ./pages/Index.vue -->
<template>
  <ul>
    <li v-for="l in list">
      <router-link :to="{ name: 'detail', params: { id: l.id }}">{{ l.title }}</router-link>
    </li>
  </ul>
</template>

<script>
import { mapGetters } from 'vuex'

export default {
  computed: {
    ...mapGetters(['list'])
  }
}
</script>


最後に「Detail.vue」
ここには「/detail/:id」のidを受け取り、その詳細情報を表示するという内容。
1つは、vue-routerの標準機能として取得できるthis.$route.params.idをつかって、this.$store.state.listから取得する方法。
他方、vuex-router-syncで取得できるstate.route.params.idを使って、gettersから取得する方法を書いている。
<!-- ./pages/Detail.vue -->
<template>
  <div>
    <h2>{{ detail.title }}</h2>

    <h3>vue-routerの機能</h3>
    <h4>$route</h4>
    <pre>{{ $route | formatJSON }}</pre>

    <h4>$store.state.listを$route.params.idで取得(createdで実行)</h4>
    <pre>{{ dataDetail | formatJSON }}</pre>

    <h3>vuex-router-syncの機能</h3>
    <h4>$store.state.route</h4>
    <pre>{{ $store.state.route | formatJSON }}</pre>

    <h4>$store.state.routeを使ってgetters.detailから取得</h4>
    <p>{{ detail | formatJSON }}</p>

    <button @click="done">Done</button>
    <router-link to="/">戻る</router-link>
  </div>
</template>

<script>
import { mapGetters, mapActions } from 'vuex'

export default {
  data () {
    return {
      dataDetail: []
    }
  },
  created () {
    this.dataDetail = this.$store.state.list.find(l => l.id.toString() === this.$route.params.id.toString())
  },
  computed: {
    ...mapGetters(['detail'])
  },
  methods: {
    ...mapActions(['done'])
  },
  filters: {
    formatJSON (val) {
      return JSON.stringify(val, null, '  ')
    }
  }
}
</script>

vue-routerのデフォルト機能としてthis.$routeでroute情報にアクセスすることができるので、vuex-router-syncを使うメリットが理解できなかったが、サンプルコードを書いたことでようやく理解できた。

また、/(ルート)から/detail/1に移動したときのstate.route.params.idはNumber型なのに対し、/detail/1でリロードするとString型に変わってしまう
なのでサンプルコードではtoStringをしているのだが、ホントはすべてString型でやるべきなんじゃないかと思う。
ちなみにNumber型になる理由は、「<router-link :to="{ name: 'detail', params: { id: l.id }}">」の「l.id」がNumber型のため。


これを実際に動かすと、以下のようになる。



vuex-router-syncについてより詳しく


サンプルコードを載せて、ひととおり動くところまで見ていただいたところで、vuex-router-syncについてもうちょっと深く掘り下げてみる。

vuex-router-syncを使うことのメリットは、store.state.routeでroute情報にアクセスできることに尽きる。vue-routerだけだとthis.$routeが参照できる範囲でしか値を取得できない。
ちなみに以下のコードが、this.$routeの内容とthis.$store.state.routeの内容。
//console.log(this.$route)
{
  "name": "detail",
  "meta": {},
  "path": "/detail/1",
  "hash": "",
  "query": {},
  "params": {
    "id": 1
  },
  "fullPath": "/detail/1",
  "matched": [
    {
      "path": "/detail/:id",
      "components": {
        "default": {
          "computed": {},
          "methods": {},
          "filters": {},
          "__file": "/Users/s-shinoda/personal/vuex-router-sync/src/pages/Detail.vue",
          "staticRenderFns": [],
          "beforeCreate": [
            null
          ],
          "beforeDestroy": [
            null
          ],
          "_Ctor": {}
        }
      },
      "instances": {},
      "name": "detail",
      "meta": {}
    }
  ]
}

// console.log(this.$store.state.route)
{
  "name": "detail",
  "path": "/detail/1",
  "hash": "",
  "query": {},
  "params": {
    "id": 1
  },
  "fullPath": "/detail/1"
}


注意点としては、store.state.routeはimmutable(不変なオブジェクト)なので、変更することができない。もしURLやクエリパラメータをコード内で更新したい場合は、$router.go()を使って対応する。たとえば、$router.go({ query: {...} })のようにすればクエリパラメータを更新することができる。



以上

written by @bc_rikko

0 件のコメント :

コメントを投稿