VueとVuexの間の値の連携の仕方

## 0. はじめに
- 最近、Vuexを使い始めて、Vue.jsからVuexに値を受け渡す時にいくつかの方法があり、どういう方法があって何が良いのかが把握できなかったので、備忘録的に記述します。
- [公式](https://vuex.vuejs.org/)がかなり詳しいので、公式で把握できる方は以下は読まなくて良いかと思います。

### 0-1. 最終的なイメージ
- 4. に今回作成したものの[最終的なイメージ](https://qiita.com/yuyasat/items/ec439bfdc078da5f4122#4-%E6%9C%80%E7%B5%82%E7%9A%84%E3%81%AB)を記載してあります。

### 0-2. 今回使用したstore
- store側のコードはほとんど同じだったので、今回使用したstoreを先に紹介します。

```js:stores/index.js
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

const state = {
  familyName: '',
  givenName: '',
}

const mutations = {
  setFamilyName (state, val) {
    state.familyName = val
  },
  setGivenName (state, val) {
    state.givenName = val
  },
}

const getters = {
  fullName: (state) => {
    return `${state.familyName}${state.givenName}`
  }
}

// 1-3で少し書き換えます。
const actions = {}

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

## 1. VueからVuexへ値を渡す
### 1-1. Vue.jsの監視プロパティ（watched property）を使う（非推奨）
- Vue.jsのwatched propertyを利用する方法。他の値で値を変えたい時など、早々に詰むので、基本的に使わない方が良い。
- Vuexのリファレンスにも記述されていない。

```html:template
<input type="text" name="familyName" v-model="familyName">
```
```js:app.js
import Vue from 'vue'
import Vuex from 'vuex'

import store from './stores'

new Vue({
  el: "#app",
  state: {
    familyName: '',
    givenName: '',
  },
  watch: {
    familyName (val) {
      store.commit('setFamilyName', val)
    },
  },
})
```
### 1-2. イベントで渡し、Vue.jsのmethodsの中でcommitする
- イベントの一覧は[こちら](https://blog-asnpce.com/technology/744)のブログが詳しいです。公式は、[こちら](https://jp.vuejs.org/v2/guide/events.html)。

```html
<input type="text" name="familyName" @input="setFamilyName">
```
```js:app.js
import Vue from 'vue'
import Vuex from 'vuex'

import store from './stores'

new Vue({
  el: "#app",
  state: {},
  watch: {},
  methods: {
    setFamilyName (e) {
      store.commit('setFamilyName', e.target.value)
    },
  },
})
```

### 1-3. イベントで渡すが、storeのactionでcommitする
- ビュー側に記述する呼び出すメソッドの引数の指定の仕方でいくつか記述が変わってきます。

```html
<input type="text" name="familyName" @input="updateFamilyName">
<!-- or -->
<input type="text" name="familyName" @input="updateFamilyName($event.target.value)">
```
```js:app.js
import Vue from 'vue'
import Vuex from 'vuex'
import { mapActions } from 'vuex'

import store from './stores'

new Vue({
  el: "#app",
  store, // mapActionsを使う場合は、Vueにstoreを追加する必要がある。
  state: {},
  watch: {},
  methods: {
    ...mapActions([
      'updateFamilyName',
    ]),
    // mapActionsを使わない場合 
    // - ビュー側で引数を指定しない場合
    // setFamilyName (event) {
    //   store.commit('setFamilyName', event.target.value)
    // },
    // - ビュー側の引数で$event.target.valueを指定する場合
    // setFamilyName (value) {
    //   store.commit('setFamilyName', value)
    // }
    // mapActionsは使わないが、actions経由で行う場合
    // updateFamilyName (e) {
    //   store.dispatch('updateFamilyName', e)
    // }
  },
})
```
```js:stores/index.js
const actions = {
  updateFamilyName({ commit, state }, e) { // ビュー側で引数を指定しない場合はeventが第二引数に入ってくる
    commit('setFamilyName', e.target.value)
  }, 
  // or
  // setFamilyName({ commit, state }, value) {
  //   commit('setFamilyName', value)
  // },
}
```

### 1-4. 双方向算出プロパティ（get, set）を用いる
- 双方向算出プロパティというものがあり、Vue.jsのcomputedに記述することができる。（[公式](https://vuex.vuejs.org/ja/forms.html#%E5%8F%8C%E6%96%B9%E5%90%91%E7%AE%97%E5%87%BA%E3%83%97%E3%83%AD%E3%83%91%E3%83%86%E3%82%A3)）
- ビューには`v-model`を利用し、Vue.jsのcomputedプロパティで`set`を定義する。
- ついでに`get`も記述することができる。（むしろgetは定義せずに`mapState`等で定義するのはうまく動かなかった）
- watched propertyでvuexのstateに格納するのはしたくないが、ビューに`v-model`はなかなか便利。イベントハンドラまで使いたくないという時に利用。

```html
<input type="text" name="familyName" v-model="familyName">
```
```js:app.js
import Vue from 'vue'
import Vuex from 'vuex'

import store from './stores'

new Vue({
  el: "#app",
  state: {},
  computed: {
    familyName: {
      get () { return store.state.familyName },
      set (val) { store.commit('setFamilyName', val) },
    },
  },
})
```
### 1-5. どのやり方が良いのか
- [Vuexの公式](https://vuex.vuejs.org/vuex.png)にでてくる図だと、stateはactionからmutationを経由して変更されるべきのように描かれているので、Vueのmethodsのなかで、`store.commit`とするのは良くないのかもしれません。
- ただし、[Vuex 入門](https://vuex.vuejs.org/ja/getting-started.html)で出てくるVuex を使った最も基本的なカウンターアプリの例では、Vueのmethodsの中でcommitしているので、場合によってはactionを飛ばすことも許容されるのだと思います。
- フロントエンドに詳しい方の意見を伺ったところ、actionでは非同期の処理を想定していることが多いため、非同期の場合はactionからcommitを行い、単にstoreにセットするだけのような場合はVueからcommitを行って良いという考え方もあるそうです。

## 2. VuexからVueへ値を渡す
### 2-1. 地道にcomputedに記述していく
- stateであれば、`store.state.stateKey`のように値を取得できるのでcomputedの関数に定義していく。
- gettersであれば、同様に`store.getters.getterMethod`のように取得できる。

```js:app.js
import Vue from 'vue'
import Vuex from 'vuex'

import store from './stores'

new Vue({
  el: "#app",
  ・・・（略）・・・
  computed: {
    familyName() {
      return store.state.familyName
    },
    givenName() {
      return store.state.givenName
    },
    // 異なるメソッド名にしたい場合の例
    firstName () {
      return store.state.givenName
    },
    fullName () {
      return store.getters.fullName
    },
  }
  ・・・（略）・・・
}
```
### 2-2. mapStateやmapGettersを使う
- 配列でstoreのkeyを渡す。

```js:app.js
import { mapState, mapGetters } from 'vuex'
import store from './stores'

new Vue({
  el: "#app",
  store, // mapStateやmapGettersを使う場合は、Vueにstoreを追加する必要がある。
  ・・・（略）・・・
  computed: {
    ...mapState([
      'familyName',
      'givenName',
    ]),
    // 異なるメソッド名にしたい場合の例
    ...mapState({
      firstName: 'givenName'
    }),
    ...mapGetters([
      'fullName'
    ]),
  }
  ・・・（略）・・・
}
```

## 3. その他
### 3-1. storeで他のgettersを使う
- 例えば、`familyName`と`givenName`に半角スペースなど不正な文字が与えられてなければ、カナも表示したいとき、`familyName`や`givenName`がvalidかどうかを判断するメソッドをgettersに記述し、両方共がvalidなときにカナを表示させるようとしているとします。
- gettersのメソッドは、第二引数に`getters`を取り、他のgettersのメソッドを利用することができます。（[公式](https://vuex.vuejs.org/ja/getters.html#%E3%83%97%E3%83%AD%E3%83%91%E3%83%86%E3%82%A3%E3%82%B9%E3%82%BF%E3%82%A4%E3%83%AB%E3%82%A2%E3%82%AF%E3%82%BB%E3%82%B9)）

```js:stores/index.js
const getters = {
  ・・・（略）・・・
  isValidFamilyName: (state) => {
    return String(state.familyName).trim() !== ''
  },
  isValidGivenName: (state) => {
    return String(state.givenName).trim() !== ''
  },
  isVisibleKana: (state, getters) => {
    return getters.isValidFamilyName && getters.isValidGivenName
  },
  ・・・（略）・・・
}
```

## 4. 最終的に
- 最終的にこんな感じのを作ってみました。
![vuejs-vuex-sample.gif](https://qiita-image-store.s3.amazonaws.com/0/121304/5ba811b1-ccf7-f5ee-8a1b-449627a77385.gif)
- `familyName`、`givenName`は1-4の方法でstoreに登録し、`familyNameKana`、`givenNameKana`は1-3の方法で登録しています。
- index.htmlとmain.jsのコードはvue-cliで`vue init`した時のままです。

```html:index.html
<!-- vue initのまま -->
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <title>vuejs-vuex-simple-sample</title>
  </head>
  <body>
    <div id="app"></div>
  </body>
</html>
```

```js:main.js
// vue initのまま
import Vue from 'vue'
import App from './App'

Vue.config.productionTip = false

new Vue({
  el: '#app',
  components: {
    App,
  },
  template: '<App/>',
})
```

```vue:App.vue
<template>
  <div id="app">
    <div>
      名前：<input type="text" name="familyName" v-model="familyName">
      <input type="text" name="givenName" v-model="givenName">
    </div>
    <div>フルネーム：{{ fullName }}</div>
    <div>firstName：{{ firstName }}</div>

    <div v-show="isVisibleKana">
      ナマエ：<input type="text" name="familyNameKana" @input="updateFamilyNameKana">
      <input type="text" name="givenNameKana" @input="updateGivenNameKana">
    </div>
    <div v-show="isVisibleKana">フルネーム（カナ）：{{ fullNameKana }}</div>
  </div>
</template>

<script>
import Vue from 'vue'
import Vuex from 'vuex'
import { mapState, mapGetters, mapActions } from 'vuex'

import store from './stores'

Vue.use(Vuex)

export default {
  name: 'App',
  store,
  computed: {
    familyName: {
      get () { return store.state.familyName },
      set (val) { store.commit('setFamilyName', val) },
    },
    givenName: {
      get () { return store.state.givenName },
      set (val) { store.commit('setGivenName', val) },
    },
    ...mapState({
      firstName: 'givenName',
    }),
    ...mapGetters([
      'fullName',
      'isVisibleKana',
      'fullNameKana',
    ]),
  },
  methods: {
    ...mapActions([
      'updateFamilyNameKana',
      'updateGivenNameKana',
    ]),
  },
}
</script>
```

```js:stores/index.js
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

const state = {
  familyName: '',
  givenName: '',
  familyNameKana: '',
  givenNameKana: '',
}

const mutations = {
  setFamilyName (state, val) {
    state.familyName = val
  },
  setGivenName (state, val) {
    state.givenName = val
  },
  setFamilyNameKana (state, val) {
    state.familyNameKana = val
  },
  setGivenNameKana (state, val) {
    state.givenNameKana = val
  },
}

const getters = {
  fullName: (state) => {
    return `${state.familyName}${state.givenName}`
  },
  isValidFamilyName: (state) => {
    return String(state.familyName).trim() !== ''
  },
  isValidGivenName: (state) => {
    return String(state.givenName).trim() !== ''
  },
  isVisibleKana: (state, getters) => {
    return getters.isValidFamilyName && getters.isValidGivenName
  },
  fullNameKana: (state) => {
    return `${state.familyNameKana}${state.givenNameKana}`
  },
}

const actions = {
  updateFamilyNameKana ({ commit, state }, e) {
    commit('setFamilyNameKana', e.target.value)
  },
  updateGivenNameKana ({ commit, state }, e) {
    commit('setGivenNameKana', e.target.value)
  },
}

export default new Vuex.Store({
  state,
  mutations,
  getters,
  actions,
})
```
- なにか間違っている箇所がございましたら、ご教授いただけますと幸いです。