VuexでFromの入力値をbindingさせる方法

Vuexを導入している場合、フォーム画面で困ることがあるので試行錯誤の結果を残しておきたいと思います。

ちなみに、環境は次の通りです。

* storeはmodule分割を前提
* バインディングヘルパーも使う
* サンプルコードはすべてVuetify(2.x)導入

# 公式の推奨方法

[フォームの扱い|Vuex] (https://vuex.vuejs.org/ja/guide/forms.html)

```Vue:Secret.vue
<template>
  <v-container>
    <v-row justify="center">
      <v-col cols="6">
        <v-text-field v-model="hoge" label="sample" />
      </v-col>
    </v-row>
  </v-container>
</template>

<script>
import { mapMutations, mapGetters } from 'vuex'
export default {
  name: 'Secret',

  computed: {
    ...mapGetters('Secret', ['GET_HOGE']),

    hoge: {
      get () {
        return this.GET_HOGE
      },
      set (val) {
        this.SET_HOGE(val)
      }
    }
  },

  methods: {
    ...mapMutations('Secret', ['SET_HOGE'])
  }
}
</script>

```

```JavaScript:modules/secret.js
export default {
  namespaced: true,
  state: {
    hoge: ''
  },

  getters: {
    GET_HOGE: (state) => state.hoge
  },

  mutations: {
    SET_HOGE (state, payload) {
      state.hoge = payload
    }
  }
}

```
v-modelを利用しながら、Vuexの厳格モードにも対応しているのでVue + Vuexっぽい？
stateをstoreへ即時登録させたいときによく使う

ただ、この調子でいくと入力項目が膨大な業務システムの場合、computedが大変な数になる

# 入力時のイベントでactionを呼ぶパターン

```Vue:Secret.vue
<template>
  <v-container>
    <v-row justify="center">
      <v-col cols="6">
        <v-text-field :value="GET_HOGE" @change="SET_HOGE" label="sample" />
      </v-col>
    </v-row>
  </v-container>
</template>

<script>
import { mapMutations, mapGetters } from 'vuex'
export default {
  name: 'Secret',

  computed: {
    ...mapGetters('Secret', ['GET_HOGE'])
  },

  methods: {
    ...mapMutations('Secret', ['SET_HOGE'])
  }
}
</script>
```
※storeは上述の内容と同じ

v-modelにgettersからもらった値を入れるとそのままstoreを更新してしまうので、valueをbindingさせる
inputに変化があった(ユーザが入力した)ら@changeで入力値をmutationに送る、という方法

こっちのほうがコードが短くなりそう


## より実践的に考えてみる

実際にはフォームの値は次のようなオブジェクト形式で持つことが多いと思う

```
{
  user: {
    name:'',
    age: ''
  }
}
```

この場合、

```Vue:Secret.vue
<template>
  <v-container>
    <v-row justify="center">
      <v-col cols="3">
        <v-text-field :value="GET_OBJ.hoge" @change="SET_OBJ_HOGE" label="sample1" />
      </v-col>
      <v-col cols="3">
        <v-text-field :value="GET_OBJ.sage" @change="SET_OBJ_SAGE" label="sample2" />
      </v-col>
    </v-row>
  </v-container>
</template>

<script>
import { mapMutations, mapGetters } from 'vuex'
export default {
  name: 'Secret',

  computed: {
    ...mapGetters('Secret', ['GET_OBJ'])
  },

  methods: {
    ...mapMutations('Secret', ['SET_OBJ_HOGE', 'SET_OBJ_SAGE'])
  }
}
</script>

```

```JavaScript:modules/secret.js
export default {
  namespaced: true,
  state: {
    obj: {
      hoge: '',
      sage: ''
    }
  },

  getters: {
    GET_OBJ: (state) => state.obj
  },

  mutations: {
    SET_OBJ_HOGE (state, payload) {
      state.obj.hoge = payload
    },
    SET_OBJ_SAGE (state, payload) {
      state.obj.sage = payload
    }
  }
}

```

みたいな感じになるのを良しとするのかはプロジェクトしだいな気がする

# componentに変数を持たせwatchするパターン

```Vue:User.vue
<template>
  <v-container>
    <v-row justify="center">
      <v-col cols="3">
        <v-text-field v-model="user.name" label="name" />
      </v-col>
      <v-col cols="3">
        <v-text-field v-model="user.age" label="age" />
      </v-col>
    </v-row>
  </v-container>
</template>

<script>
import { mapMutations, mapGetters } from 'vuex'
export default {
  name: 'User',

  data () {
    return {
      user: {
        name: '',
        age: ''
      }
    }
  },

  computed: {
    ...mapGetters('User', ['GET_USER'])
  },

  watch: {
    user: {
      handler: function (val) {
        this.SET_USER({ name: val.name, age: val.age })
      },
      deep: true
    }
  },

  mounted () {
    this.user.name = this.GET_USER.name
    this.user.age = this.GET_USER.age
  },

  methods: {
    ...mapMutations('User', ['SET_USER'])
  }
}
</script>
```

```JavaScript
export default {
  namespaced: true,
  state: {
    user: {
      name: '',
      age: ''
    }
  },

  getters: {
    GET_USER: (state) => state.user
  },

  mutations: {
    SET_USER (state, payload) {
      state.user.name = payload.name
      state.user.age = payload.age
    }
  }
}
```

これは力業すぎて嫌われそう。

# Submit時にstoreに登録するパターン

```Vue:User.vue
<template>
  <v-container>
    <v-row justify="center">
      <v-col cols="3">
        <v-text-field v-model="user.name" label="name" />
      </v-col>
      <v-col cols="3">
        <v-text-field v-model="user.age" label="age" />
      </v-col>
    </v-row>
    <v-row>
      <v-col cols="3">
        <v-btn block @click="submit(user)">登録</v-btn>
      </v-col>
    </v-row>
  </v-container>
</template>

<script>
import { mapActions, mapGetters } from 'vuex'
export default {
  name: 'User',

  data () {
    return {
      user: {
        name: '',
        age: ''
      }
    }
  },

  computed: {
    ...mapGetters('User', ['GET_USER'])
  },

  mounted () {
    // 初期値
    this.user.name = this.GET_USER.name
    this.user.age = this.GET_USER.age
  },

  methods: {
    ...mapActions('User', ['submit'])
  }
}
</script>
```

わりと業務で使われているイメージ
入力途中の値をstoreに残したくない(必要がない)ときは、これでも良さそう

# まとめ

個人的にはロジックはstoreに押し込みたいので、

* イベントを利用するパターン
* Submit時にstore登録するパターン

が好き。

Vueを使い始めのころはwatchが楽しくて多用していたけど、今思えばあんまりよくない方法だったと反省しています。

あと、MutationじゃなくてActionを呼べっていう人もいると思うので、何が正解かこれからも考えていきたいです。