Vuexについてまとめ、使ってみる。

# この記事は？

Vue.jsでは状態管理ライブラリとしてVuexが提供されています。
この記事では以下のようなことをまとめ、VuexのHello Worldとして、Vue.jsで作成したカウンターアプリにVuexを追加してみようと思います。

* クライアントサイドでのコンポーネント間における状態管理の必要性
* Vueのアーキテクチャ


![logo.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202545/e64f2db1-a236-9fac-a67c-cdeaa280baf4.png)


# クライアントサイドでのコンポーネント間における状態管理の必要性
昨今、SPA(Single Page Application)と呼ばれるWebサイトの構築手法が流行し、旧来サーバーサイドで実行していたロジックの一部がクライアントサイドへ移行してきています。
そのような流れの中で、クライアントサイドの状態管理には以下のような特性が強くなりました。

* 複数のビューが同じ状態に依存する
* 異なるビューからのイベントで、同じ状態を更新する

ここで、上記のような特性を考慮した状態管理が行われていないと、画面の一部が更新されないといったようなバグの原因になります。 
素のVue.jsも状態管理の機能は持っていますが、MVVMと呼ばれる状態管理パターンを採用しておりこのパターンは複数のコンポーネント間の状態管理を行うことに問題があると考えられています。
そこで登場するのが、Vuexです。

# Vuexとは
VuexはVue.jsのために作成された状態管理ライブラリです。ちなみに読み方は「びゅーえっくす」みたいです。

## Vue.jsの状態管理との違い
上記のようにVue.jsにも状態管理の機能はあります。これは、MVVMと呼ばれるパターンを採用しており、図示すると以下のようになります。

![MVVM.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202545/c93cbc96-ef08-8003-9347-e62bf00429c2.png)


このパターンは双方向バインドを可能とし、Viewの更新された場合も、Modelが更新された場合でもお互いにイベントを検知することができます。これはコンポーネントが少ない状態であればよく働きます。
しかし、ここで、コンポーネントが増えていくと以下のような状態になっていきます。

![MVVM-complicated.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202545/c84a55c9-53b4-2ac5-7266-fcf903bf5a0f.png)


状態の依存関係が複雑になりメンテナンス性を落とします。上記の図ではわかりづらく感じるので、どういうことなのかもう少し掘り下げてみます。
Vue.jsはコンポーネントシステムを採用しており、コンポーネントには親子関係が存在します。
ここで、親コンポーネントは子コンポーネントへ状態を受け渡す必要があり、さらに状態はそれぞれのコンポーネントで状態管理されます。

![vuecomp.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202545/8438eb68-61c9-f226-bebe-ea4efefe4dbe.png)

このような状況下では以下のような問題が起こります。

* 親コンポーネントから子コンポーネントへの状態の受け渡しが煩雑になる。
* 状態変更イベントが複雑化しメンテナンス性がさがる。

後者の一例を示すと、状態変更イベントの通知は下の図のような流れで実施しなければなりません。

![vuecomp2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202545/0b2073c4-8a4b-f1a0-d017-ddff76baff52.png)

例はまだ単純ですが、業務アプリの場合コンポーネントは多数で依存関係はさらに複雑になることがあり得ます。
別画面の更新で、同じ状態を同期的に変更していくのは非常に難しくなっていくでしょう。
このような問題に対してVuexではFlux、Redux、The Elm Architectureから影響を受けた以下のような状態管理を行います。

![vueflux.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202545/1d675bdf-cf10-af10-dae4-659433bc1da5.png)

グローバルに参照できるストアを用意することで、Vueインスタンスのそれぞれが同じデータにアクセスすることができ、値の受け渡しなどの複雑性を排除します。
また、更新の流れが一方向であり、かつ、更新時の役割を分割することによってメンテナンス性を向上させることができます。

## Vuexのアーキテクチャ

VuexではVue.jsの状態管理とは違い以下の概念が登場します。

* StateとStore
* Action
* Mutations
* Getter

### StateとStore
StateはVuexでコンポーネント間でグローバルな状態を持つ単一のステートツリーです。StateはStoreで管理されます。
Stateはアプリケーション内で1つだけ作成されます。そのため、アプリケーションが信頼できる唯一の情報源として機能します。
ここで、Vuexではすべての状態をStateに入れて管理をすべきであるとは言っておらず状態が1つのコンポーネントで完結しているのであればStateに状態を入れておく必要はありません。

### Mutations
VuexのStateを変更する唯一の方法はMutationをコミットすることです。

```javascript

const store = new Vuex.Store({
  state: {
    count: 1
  },
  mutations: {
    increment (state) {
      // 状態を変更する
      state.count++
    }
  }
})
```

Mutationはイベントに近い概念を持っておりタイプとハンドラを持ちます。
例えば上記のコードでは`increment`がタイプで、メソッド部分を含めるとハンドラになります。
Mutationは直接呼び出すことはできず下記のようにミューテーションタイプを指定しコミットすることになります。

```javascript

store.commit('increment')
```

また、状態変更を非同期に組み合わせることは、プログラムの動きを予測することを非常に困難にするため、Mutationでは非同期な処理を記述することはできません。
Vuexすべての状態変化は同期的に行うということが作法となっています。

### Action
アクションではMutationのコミットを行います。
アクションではMutationと違い非同期な処理を書くことができます。

```javascript
const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    increment (state) {
      state.count++
    }
  },
  actions: {
    increment (context) {
      context.commit('increment')
    }
  }
})
```

アクションでは`context`オブジェクトを受けとります。`context.commit`でMutationをコミットすることもでき、`context.getter`でgetterにアクセスすることも可能です。
アクションそのものは、`store.dispatch`がトリガーとして実行されます。

```javascript

store.dispatch('increment')
```

`context.commit`を直接呼び出さないのはAction内で非同期な処理が記述されることが可能であるからです。

```javascript

actions: {
  incrementAsync ({ commit }) {
    setTimeout(() => {
      commit('increment')
    }, 1000)
  }
}
```

### Getter
例えばStateの状態からフィルタリングして値と取り出したいと考えたときに、Getterが使えます。

```javascript


const store = new Vuex.Store({
  state: {
    todos: [
      { id: 1, text: '...', done: true },
      { id: 2, text: '...', done: false }
    ]
  },
  getters: {
    doneTodos: state => {
      return state.todos.filter(todo => todo.done)
    }
  }
})
```

Getterは以下のように呼び出すことが可能です。

```javascript

store.getters.doneTodos
```

Getterを利用することにより、処理の再利用性を高めることができます。

# Hello World
Vue.js製のカウンターアプリにVuexを追加してみたいと思います。

## 環境
* Vue CLI(v3.11.0)
* Node(v11.9.0)
* Chrome

## 前回の記事で作ったカウンターアプリ
「 [Vue.jsについてまとめて、カウンターアプリを作ってみる。](https://qiita.com/hirooka0527/items/ebd41447776b526074b9) 」で作成したカウンターアプリに対してVuexを導入します。そこまで複雑なアプリでないので、この記事を先に読むことは必須ではないです。しかし、作成されているカウンターアプリの構造に把握していることを前提に進めますので、ソースコードに一度目を通してみてください。(そんなに時間はかからないと思います。)

* [hello-world-counter](https://github.com/samuraiball/vue-sandbox/tree/master/hello-world-counter)


## Vuexの導入

まずはVuexをインストールします。

```
$ npm install vuex --save
```

`package.json`に`vuex`の依存が追加されました。

```
  "dependencies": {
    "core-js": "^2.6.5",
    "vue": "^2.6.10",
    "vuex": "^3.1.1"
  }
```

次にStoreを作成します。StoreはVuexのStateを管理するためのコンテナです。
`counter.js`という名前でファイルを作成し以下のコードを記述します。

```javascript

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store(
    {
        state:{
            num: 0
        },
        mutations:{
            increment(state) {
                state.num++
            },
            decrement(state) {
                state.num--
            }
        },
        actions:{
            increment(context) {
                context.commit('increment')
            },
            decrement(context) {
                context.commit('decrement')
            }
        }
    }
)
```

まず、`Vue.use(Vuex)`の箇所ではVue.jsに対しプラグインの設定を行いってます。今回はVuexのプラグインを使うのでその登録を行っています。
`Vuex.Store(....)`の箇所で実際にStoreを作成します。`state`と`mutations`、`actions`をそれぞれ定義しています。今回は`getter`は必要ないので作成しませんでした。

上記で作成したStoreを`main.js`で読み込ませます。

```javascript


import Vue from 'vue'
import App from './App.vue'
import store from './store'

Vue.config.productionTip = false

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

その後作成したStoreのActionを呼び出すDispatch部分と、初期値のStateをStoreから取り出す部分の記述を行います。

```vuejs
<template>
    <div id="counter">
        <span>{{ num }} </span>
        <button @click="increment">up</button>
        <button @click="decrement">down</button>
        <p v-if="num === 0">
            Press 'up' or 'down' button!!!!
        </p>
    </div>
</template>

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

    export default {
        name: 'Counter',
        computed: mapState({
            num: state => state.num
        }),
        methods: mapActions([
            'increment',
            'decrement'
        ])
    }
</script>
```

`computed`は算出プロパティーと呼ばれるもので、この内部で定義されている`num`が変更された際に依存関係にあるコンポーネントの部分が更新される仕組みになっています。
`computed`ではさらに`mapState`と呼ばれるVuexで用意されているヘルパー関数を利用しています。このヘルパー関数を利用することによって複数のStateを利用する際の冗長なコードを削減することができます。
例えば、`mapState`を使わないと複数Stateを扱いたい場合に以下のような実装になります。

```javascript

// mapStateを使わない場合
computed:{
    hoge(){
        return this.$store.state.hoge
    },
    fuga(){
        return this.$store.state.fuga
    }
}

// 使った場合
computed: mapState({
            hoge: state => state.hoge,
            fuga: fuga => state.fuga 
        }),
```
今回は、Stateが1つしかないのであまり使う意味はなかったかもしれません。。。

次に、`methods`ですが、ここではActionの呼び出しを行っています。ここでもヘルパー関数の`mapAction`を利用しています。
`mapActions`を使わない場合`this.$store.dispatch("ACTION_TYPE")`を呼び出す関数をそれぞれ定義することになりこれも冗長になります。

ひとまず、ここまでで[以前作成したカウンターアプリ](https://qiita.com/hirooka0527/items/ebd41447776b526074b9)にVuexを導入できました。
ただ、これだけだとVuexを導入した面白味に欠けるので、1つコンポーネントを作成して、`num`プロパティを複数個所から読み込めるようにしてみたいと思います。
`num`に任意の数字を足し引きできるフォームを追加します。
まずは`counter.js`にActionとMutationを追加します。

```javascript


mutations:{
    increment(state) {
        state.num++
    },
    decrement(state) {
        state.num--
    },
    // 追加
    calc(state, n){
        state.num +=n
    }
},
actions:{
    increment(context) {
        context.commit('increment')
    },
    decrement(context) {
        context.commit('decrement')
    },
    // 追加
    calc(context, n){
        context.commit('calc', n)
    }
}
```

次にフォーム用のVueコンポーネントを1つ追加します。
`CalcNumForm.vue`を作成しまします。

```vuejs
<template>
    <div>
        <input v-model="calcNum" placeholder="type number">
        <button @click="calc(calcNum)" >calculate</button>
    </div>
</template>

<script>
    import {mapState} from 'vuex'

    export default {
        name: "CalcNumForm",
        computed: mapState({
            num: state => state.num
        }),
        methods: {
            calc: function (calcNum) {
                this.$store.dispatch('calc', parseInt(calcNum))
            }
        }
    }
</script>
```
まず、`template`タグの`input`の部分ですが、`v-model`を利用しています。
`v-model`はVue.jsのタグディレクティブでinput要素などで、双方向バインディングを作成してくれます。
次に`script`タグの中で、ステートのバインディングとActionの呼び出しを行っています。
今回、Actionの引数として、入力された値を渡したかったので、呼び出しには`mapActions`を使わずに`this.$store.dispatch`を呼び出しています。
(`mapActions`でもできるのかもしれませんが、やり方がいまいちわからなかったです。)
そして、作成したコンポーネントを`App.vue`から呼び出して表示します。
これで動くはずです。

Devサーバを起動して動かしてみます。

![counter1.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202545/3208e62f-1a63-55d9-bbd2-ed0cb3828743.jpeg)
![counter2.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202545/9ffaf4f8-1ee1-c4be-4e68-b9f7284151ee.jpeg)
![counter3.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202545/11150d87-086d-d551-61b3-4d5db4fd66d0.jpeg)


動きました。
実はこのアプリは入力ボックスに数値以外のものを入れてしまうと`NaN`になってしまうのですが、ここではその対応までは作りこみません。
一旦、VuexへのHello Worldはできた気がします。

# 感想
今回使用したカウンターアプリではVuexの利点はわかりづらいかもしれませんが、業務レベルの巨大なアプリケーションになればもう少しVuexの利点を身近に感じれるのではないかと感じました。
あとは、今回は触りませんでしたが、Stateが大きくなってきた際にその定義を分割できるモジュールと呼ばれる機能もあるようです。

## 参考資料

* [Vuex公式サイト](https://vuex.vuejs.org/ja/)
* [flux-concepts](https://github.com/facebook/flux/tree/master/examples/flux-concepts)
* [いまさら学ぶMVVMパターン](https://www.slideshare.net/YutaMatsumura/mvvm-77221171)
* [MVCの流れを簡単にまとめてみる](https://qiita.com/hirokidaichi/items/0de5ca336de862cc91bd)