構造の複雑さとVuex書き分け

  • 63
    いいね
  • 0
    コメント

Vue.js Advent Calendar 2016 14日目です。

Vue2 + Vuex2の組み合わせはVueを正しくfluxに導き、より巨大なアーキテクチャを管理できるようになった。
逆に言うとVueの気軽さが失われてしまったのでは、という印象を持つ方もいらっしゃるのでは。

しばらく使ってみたら肩の力の抜き方が分かってきたので、構造の複雑さ別に書き方をまとめてみる。

このポストは

"vue": "^2.1.4"
"vuex": "^2.0.0"

の提供でお送りします。

Level 1

子コンポーネントとか存在しない。単一のVueコンポーネントで完結する。
勿論Vuexも要らない。

main.js
new Vue({
  data: {
    hello: false
  },
  methods: {
    sayHello () {
      this.$emit('hello')
    }
  },
  created () {
    this.$on('hello', () => {
      this.$data.hello = true
    })
  }
})

イベントを介さずお互いにthis.methodName()で呼び出しても良い気もする。

Level 2

いくつかの子コンポーネントが存在するが、Vuexは使わない。
v-onディレクティブによって子コンポーネントからイベントを親に伝達することができる。

<hello-component @my-event="onHelloEvent"></hello-component>

ちょっとした子→親へのイベント伝達ならこれでも良いが、スケールしづらい。

Level 3

いくつか子コンポーネントが存在し、何層かの構造が出来つつある複雑さ。
管理するためにVuexを導入してみる段階。
モジュールのないStore程度なら以下の通り適当でもやっていける。

Vuex exampleだとtodomvcの書き方が丁度良いのではないでしょうか。

store.js
// 基本的なStore部分
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    hello: true
  },
  mutations: {
    bye (state) {
      state.hello = false
    }
  }
})

Getters と computed

複数コンポーネントでGettersを使い回す予定が無いなら、gettersに書かずにcomputedで宣言してもいい。

app.js
import * as types from './store/types'

export default {
  computed: {
    hello () {
      return this.$store.state.hello
    }
  }
}

Actions と methods

methodsの中でcommitも邪道ではない。
StoreにActionsを書かなくてもやっていける。

methods
methods: {
  sayHello () {
    this.$store.commit('hello')
  }
}

更に、Mutationsをmethodsの中に呼び出せる。
こんがらがったらちゃんと分割した方が良さそう。

methods
methods: {
  sayHello () {
    this.$emit('hello')
  },
  ...mapMutations([
    'bye'
  ])
}
html
<button @click="bye">BYE</button>

適当さ加減の原動力は「ストアとコンポーネントの行き来が面倒」に尽きる。

Level 4

複雑にデータもコンポーネントも重なっていてヤバい。

Gettersで出力を管理し、
Mutationsもファイルに分けて書くし、
MutationTypesもモジュールごとに定数で管理する。

モジュール分割や命名など、要件によって変わると思うので個人的に頻出するものを書く。

名前空間

モジュールごとに名前空間を付けないと管理しきれない。

modules/hello/type.js
const namespace = 'hello/'
export const HELLO  = namespace + 'HELLO'
export const SAY_HELLO   = namespace + 'SAY_HELLO'
export const TOGGLE_DONE = namespace + 'TOGGLE_DONE'
modules/hello/index.js
import * as types from './types'

export default helloModule = {
  state: {
    hello: false,
  },
  getters: {
    [types.HELLO] (state) {
      return state.hello
    }
  },
  actions: {
    [types.SAY_HELLO] (context, payload) {
      api.sayHello()
        .then((data) => {})
        .catch((err) => {})
    }
  },
  mutations: {
    [types.HELLO] (state, payload) {
      state.hello = true
    }
  }
}

actionsもgettersも定数になるのは個人的に気持ち悪いのですが、
多分これが一番混乱しないと思います。

Data と State

ex.js
export default {
  data () {
    isLoading: false,
    isVisible: false
  }
}

個々のコンポーネントが持つdataの使い所とは。
「読み込み中はローダーを表示する」時、StoreにViewのためにisLoadingが生えるのは宜しくない。
methods上でquerySelector()するよりは、dataisLoadingを生やして、v-classで状態管理した方がよさげ。

個人的には、アプリケーションとしてのデータに関係ないが見た目を管理したい時はmethodsdataで管理し、データのロジックはActionsとMutationsに任せるよう分けている。

まとめ

ドキュメントや使ってみたでは行儀の良い書き方が書かれていて「Vue2.0で書き方が複雑になってるのでは」と尻込みしてしまうこともあるかもしれないが、慣れてくるとそんなこともなかった。
個人レベルでLevel 4までガッチガチに設計固めて作る必要も無いので、程度に肩の力を抜いてVuexを楽しんでほしい。
Vueは2.0でも適当に書いてなんとかなるフレームワークです。

参考資料

Vuex Docs
Vuex examples