カウンターを作りながら，VueとVuexの基本をやる

# はじめのはじめに
この記事は
「[IDOM Engineer Advent Calendar 2017](https://qiita.com/advent-calendar/2017/idom-engineer)」
の23日目の記事です．

---
# はじめに
今回の記事の対象はこんな感じです．

- Vue使ったことない
- Vueは使ったことあるけど，Vuexはない

簡単なカウンターを作りながら説明していきます．
作ったカウンターのリポジトリはこちら(https://github.com/ferretdayo/vuex-counter)

VueとVuexのバージョンは以下の通りです．

- Vue: 2.5.2
- Vuex: 3.0.1

何か間違っている所などあればご意見よろしくお願いします．

--- 
# まずVueプロジェクトの作成
- vue-cliのインストール

これを利用すると簡単にvueのプロジェクト作成出来るようになります．

```sh:cmd
npm install -g vue-cli
```

プロジェクトの作成をします．
vue-cliの詳細は[こちら](https://github.com/vuejs/vue-cli)から

```sh:cmd
vue init webpack counter
```

---
## フォルダ構成
初期のsrcのフォルダ構成はこんな感じです

```sh:
src/
├── App.vue
├── assets
│   └── logo.png
├── components
│   └── HelloWorld.vue
├── main.js
└── router
    └── index.js
```

---
## 必要なパッケージのインストール
プロジェクトのディレクトリに入り，以下のコマンドを実行します．
vuexは最初から入ってないので追加で入れます．

```sh:cmd
yarn install
yarn add -D vuex
```

---
## ひとまず動かす

```sh:cmd
yarn start
```

![image.png](https://qiita-image-store.s3.amazonaws.com/0/116983/b5dc9e3c-5438-c04f-c345-9e1746c59212.png)

---
# カウンター機能を作成
やることとしてはこの2つです．ひとまずVuexは使わずにVueのみでやります．

- Counterコンポーネントを作成し，カウンター機能の追加
- HelloWorldコンポーネントでCounterコンポーネントを利用

---
## Counterコンポーネントを作成し，カウンター機能の追加
以下のように，`src/components/Counter.vue`を作成しましょう．

ここでの説明

- data()の中の変数は`{{count}}`のようにすることで表示できる
- clickイベントは`v-on:click`で設定できる
- 関数は`methods`に書いていく
- `<style scoped></style>`のscopedはCSSの影響範囲をこのコンポーネント内にできる．[コンポーネントスコープ CSS（Scoped CSS）](https://jp.vuejs.org/v2/guide/comparison.html#HTML-amp-CSS)

```js:src/components/Counter.vue
<template>
  <div id="counter">
    <div>{{count}}</div>
    <button v-on:click="decrement">-1</button>
    <button v-on:click="increment">+1</button>
  </div>
</template>

<script>
export default {
  name: 'Counter',
  data () {
    return {
      count: 0
    }
  },
  methods: {
    increment () {
      this.count++
    },
    decrement () {
      this.count--
    }
  }
}
</script>

<style scoped>

</style>
```

---
## HelloWorldコンポーネントでCounterコンポーネントを利用
次は，既にある`src/components/HelloWorld.vue`を編集します．

ここでの説明

- 必要なComponentをimportする
- `components: {}`にimportしたCounterコンポーネントを追加することで，`<counter></counter>`のように利用できる

```js:src/components/HelloWorld.vue
<template>
  <div class="hello">
    <counter></counter>
  </div>
</template>

<script>
import Counter from './Counter'

export default {
  name: 'HelloWorld',
  components: {
    Counter
  }
}
</script>

<style scoped>

</style>
```

もし，`<counter></counter>`を`<my-counter></my-counter>`にしたい場合は，`components:{}`を以下のようにすればいける

```js
export default {
  name: 'HelloWorld',
  components: {
    my-counter: Counter
  }
}
```

---
## 動いてるか見てみる
ちゃんと+1と-1が動いた！

![image.png](https://qiita-image-store.s3.amazonaws.com/0/116983/c1f10ded-9fc6-104d-67dc-a13ed69f6232.png)

---
# Vuexとは
概要は公式の図から．要は図のようなデータの流れを持つ，状態管理のパターンです．

1. ComponentはdispatchすることでActionsを実行する
  - (ex：incrementする，decrementするというAction)
2. ActionsはCommitすることでMutationsを利用する
  - (ex：incrementやdecrementでcountの増減の実行)
3. MutationsはStateを変更する
  - (ex：countの増減)

![image.png](https://qiita-image-store.s3.amazonaws.com/0/116983/6139aa58-4d0a-15e6-059f-4669b640ce2d.png)

---
## どういうときにVuexが必要？
コンポーネント間のデータの受け渡しが大変になってきた時に必要というか欲しくなる．．．
あとデータの流れがわからなくなった時にVuexを利用すると，データの流れが１方向なので，わかりやすくなる．

例えば，Vuexを利用しない時のコンポーネント間のデータ受け渡しは以下の感じ．
![image.png](https://qiita-image-store.s3.amazonaws.com/0/116983/217cf6f2-b0b6-ab9f-4313-15d04a92c522.png)

つまり，データバケツリレーする必要がある．

---
# カウンター機能にVuexを利用
やることとしては，以下の３つあります．

- storeファイルを作成し，state, actions, mutations, gettersを定義
- rootコンポーネントのmain.jsにstoreファイルに書かれたstoreを登録
- CounterコンポーネントからVuexを利用

最終的なsrcのフォルダ構成は以下の通り．

```
src
 ├── App.vue
 ├── assets
 │   └── logo.png
 ├── components
 │   ├── Counter.vue
 │   └── HelloWorld.vue
 ├── main.js
 ├── router
 │   └── index.js
 └── store.js
```

---
## storeファイルを作成
まずactionsやmutations，getters，stateを持つ`src/store.js`を作成します．

ここでの説明

- stateには状態を持ちたい情報を定義する
- actionsでアクションを定義，commitすることでmutationsが実行される
- gettersはコンポーネントがデータを取得する際に利用される
- mutationsはstateの状態を変更する際に利用される
- gettersとmutationsのメソッドの引数には定義したstateが代入される
- actionsのメソッドの引数のcontextにはdispatch，commit，getters，stateを持つオブジェクトが代入される
    - 引数に`{ commit }`とすると，`commit`だけ利用できる．
    - 詳しくはES2015の[分割代入](http://www.yoheim.net/blog.php?q=20160805)

```js:src/store.js
import Vuex from 'vuex'
import Vue from 'vue'

Vue.use(Vuex)

/**
 * 状態を保持したい変数の管理
 */
const state = {
  count: 0
}
/**
 * actionsはmutationsを利用して，アクションの処理を実装
 */
const actions = {
  increment (context) {
    context.commit('increment')
  },
  decrement ({ commit }) {
    commit('decrement')
  }
}
/**
 * gettersはstateの値を取得するのに利用
 */
const getters = {
  getCount (state) {
    return state.count
  }
}
/**
 * mutationsは値の移り変わりの処理を実装
 */
const mutations = {
  increment (state) {
    state.count += 1
  },
  decrement (state) {
    state.count -= 1
  }
}

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

```

---
## rootコンポーネントにstoreを登録
どこでもstoreにアクセスできるようにrootコンポーネントであるmain.jsに登録します．

```js:src/main.js
import Vue from 'vue'
import App from './App'
import router from './router'
import store from './store'

Vue.config.productionTip = false

/* eslint-disable no-new */
new Vue({
  el: '#app',
  router,
  store,
  template: '<App/>',
  components: { App }
})
```

---
## CounterコンポーネントからVuexの利用
Main.jsで追記したstoreを利用するには`this.$store`から利用します．
個人的には，dispatchとgettersだけを利用して，stateとcommitはstore.jsだけでアクセスするようにしたほうがごちゃごちゃせずにいい気がします．
また，`this.$store`は，以下の４つにアクセスできます．

- state
    - stateにアクセスする際に利用
- dispatch
    - actionsで定義したメソッド名を引数に渡すことで，actionを実行できる
- getters
    - ここからgettersで定義したメソッドを利用できる
- commit
    - mutationsに定義したメソッド名を引数に渡すことで，mutationを実行できる

データの取得にはcomputedで，storeのgettersで定義したcountの値を返すgetCountメソッドを利用する．
computedの理由は，変更検知したときに実行されるためです．
computedの説明は以下のように公式にもあります．

>算出プロパティはキャッシュされ、そしてリアクティブ依存が変更されたときにだけ再算出します。

```js:src/components/Counter.vue
<template>
  <div id="counter">
    <div>{{count}}</div>
    <button v-on:click="decrement">-1</button>
    <button v-on:click="increment">+1</button>
  </div>
</template>

<script>
export default {
  name: 'Counter',
  computed: {
    count () {
      return this.$store.getters.getCount
    }
  },
  methods: {
    increment () {
      this.$store.dispatch('increment')
    },
    decrement () {
      this.$store.dispatch('decrement')
    }
  }
}
</script>

<style scoped>

</style>
```

---
## Vuexを利用したCounterを動かしてみる
スクショ見せるまでもないですが，さっきと同じ動作ができていると思います．
![image.png](https://qiita-image-store.s3.amazonaws.com/0/116983/063fa595-f4da-963d-dcda-b1ee36418d23.png)

---
# Vuexのコードを少し発展させる
actionsを利用するために，methodsに`increment()`と`decrement()`メソッドを書きました．
しかし，actionsが増えるたびにメソッド追加するのはめんどうです．

ということで，`mapActions`をインポートし，利用することで以下のように書き換えることもできます．
Vuexは`mapActions`以外にも，`mapState`，`mapGetters`，`mapMutations`があります．

```js:src/components/Counter.vue
<template>
  <div id="counter">
    <div>{{count}}</div>
    <button v-on:click="decrement">-1</button>
    <button v-on:click="increment">+1</button>
  </div>
</template>

<script>
import { mapActions } from 'vuex'

export default {
  name: 'Counter',
  computed: {
    count () {
      return this.$store.getters.getCount
    }
  },
  methods: {
    ...mapActions([
      'increment',
      'decrement'
    ])
  }
}
</script>

<style scoped>

</style>
```

もし，mappingの名前を変えたい場合は，以下のようにすることでできます．
こうすることで，addメソッドを呼べば，actionsにあるincrementメソッドを呼び出すようになります．

```js
...mapActions({
  add: 'increment',
  sub: 'decrement'
})
```

---
# おわりに
今回はカウンター機能をVueを触りつつ，Vuexを利用して開発しました．
これで大体VueとVuexの概要がわかった気になりました．
`store.js`にあるactionsやmutations，stateの量が多くなった場合は大変になりそうなので，その時はどうするのかが今後考えなきゃいけなさそうですね．
まぁ[公式](https://vuex.vuejs.org/ja/modules.html)に書いてるので，それをやるだけですがｗ