これからはじめるVue.js実践入門
posted with ヨメレバ
山田 祥寛 SBクリエイティブ 2019年08月23日頃
Vuex
Vuexの準備
- npx vue create xxxでのプロジェクト作成時に有効にする。
(既存のプロジェクトにはnpx vue add vuexで追加する) - src/store/index.jsに雛形が生成される。
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
},
getters: {
},
mutations: {
},
actions: {
},
modules: {
}
})
Vuexを使ったカウンターのサンプル
1. src/store/index.jsのstateにデータ本体を定義し、mutationsにステートを操作するためのメソッドを定義する。
struct: true,
とするとstrictモードとなり、ミューテーション以外からのステート更新に対して警告が表示される。
ただし、オーバーヘッドが大きくなるため本番環境では無効にするべき。
export default new Vuex.Store({
// struct: true,
state: {
count: 0
},
mutations: {
minus(state) {
state.count--;
},
plus(state) {
state.count++;
}
},
})
2. ストアをアプリに登録する。
- 登録すると、すべてのコンポーネントから
this.$store.xxx
でVuexストアにアクセスできるようになる。 - Vue CLIではmain.jsで登録されているので、ソースの修正は不要。
import store from './store'
〜省略〜
new Vue({
store,
render: h => h(App)
}).$mount('#app')
3. コンポーネントからストアを呼び出す。
- ミューテーションはcommitメソッド経由で呼び出す。
{{ $store.state.count }}
のようにテンプレートから直接ストアにアクセスすることもできるが、お作法として算出プロパティに定義するべき。
※ 後述するmapState, mapMutationsヘルパー関数を使うのが一般的。
<template>
<div id="app">
<h1>{{ count }}</h1>
<button @click="plus">+</button>
<button @click="minus">-</button>
</div>
</template>
<script>
export default {
name: 'App',
computed: {
count() {
return this.$store.state.count;
}
},
methods: {
plus() {
this.$store.commit('plus');
},
minus() {
this.$store.commit('minus');
}
}
}
</script>
mapStateヘルパー関数
- ステートを算出プロパティとして定義するのは面倒なため、VuexにはmapStateヘルパー関数が用意されている。
import { mapState } from 'vuex'
//として以下のようにステートのプロパティ名を指定するだけ。
computed: mapState(['count']),
//他の算出プロパティとまとめる場合はスプレッド演算子を使う。
computed: {
hoge() {
return this.name;
},
...mapState(['count']),
}
computed: {
count() {
return this.$store.state.count;
}
},
// 上記の記述を以下のように省略して記述できるようなる。
computed: mapState(['count']),
// ステートと違う名前の算出プロパティにしたい場合はオブジェクトにする。
computed: mapState( { countNum: 'count' } ),
Vuexを構成する要素
ゲッター(getters)
- ステート用の算出プロパティのようなもの。
- ステートの値を加工した値を取得する場合、コンポーネントの算出プロパティを利用してしまうと各コンポーネントにコードが散乱してしまう。
ゲッターを使うことで、コードをストアにまとめることができる。
// ストアで定義
getters: {
tenTimes(state) {
return state.count * 10;
}
}
// コンポーネントでmapGettersを使って算出プロパティにマージする。
import { mapGetters } from 'vuex'
// 〜省略〜
computed: {
...mapGetters(['tenTimes']),
},
ゲッターで他のゲッターを使う場合
- 第2引数でゲッター群を受け取ることができる。
getters: {
tenTimes(state) {
return state.count * 10;
},
twentyTimes(state, getters) {
return getters.tenTimes * 2;
},
}
引数を受け取る場合
- 値をreturnするのではなく、値を返す関数をreturnする。
getters: {
multiply(state) {
return num => {
return state.count * num;
}
},
}
- 引数を受け取るゲッターは、コンポーネントでmapGettersを使うとcomputedではなくmethodsとして定義されることになる。
computed: {
...mapGetters(['multiply']),
},
// は以下と同じ意味になる。
methods: {
multiply(num) {
return this.$store.getters.multiply(num);
}
}
// よって、mapGettersを使うとcomputedではなくmethodsとなるためキャッシュされない。
// キャッシュさせたい場合は、mapGettersを使わずに算出プロパティとして定義する必要がある。
computed: {
multiply() {
return this.$store.getters.multiply(this.num);
}
},
ミューテーション(mutations)
- ミューテーションはステートを操作するためのメソッドである。
- ミューテーションは引数を持つことができる。 この引数のことをペイロードと呼ぶ。
- ペイロードはオブジェクトにするのがお作法。
mutations: {
plusX(state, payload) {
state.count += payload.num;
}
},
methods: {
plusX() {
this.$store.commit('plusX', {num: 2});
},
},
mapMutationsヘルパー関数
methods: {
plus() {
this.$store.commit('plus');
},
minus() {
this.$store.commit('minus');
}
}
// は以下のように書くことができる。
import { mapMutations } from 'vuex'
// 〜省略〜
methods: {
...mapMutations(['plus', 'minus']),
}
ステートに対しての双方向バインディング
v-model="$store.state.name"
のようにステートをそのままv-modelに渡すべきではない。
(strictモードではエラー)- 算出プロパティのゲッターとセッターを定義してステートの取得・更新を行うようにする。
<input type="text" v-model="originCount">
// 〜省略〜
computed: {
originCount: {
get() {
return this.$store.state.count;
},
set(value) {
return this.$store.commit('setCount', {count: value});
}
}
},
アクション(actions)
- ミューテーションに非同期処理を含めてはいけない。
(たとえば、連続してミューテーションが呼び出された場合、どの順番でステートが更新されるかは保証されなくなってしまう) - 非同期処理はアクションに定義し、アクションの中でミューテーションを呼び出す。
(アクションは状態を変更するのではなく、ミューテーションをコミットする) - ある程度の規模のアプリでは非同期処理の有無に関わらず、アクション経由での更新に統一するとよい。
- アクションは引数としてコンテキストオブジェクト(commit, dispatch, getters, state, rootGetters, rootStateなどのメンバーを持っている)を受け取る。
分割代入を利用してsetCountAsync({ commit }, payload) {
のように書くことが多い。
actions: {
setCountAsync(context, payload) {
setTimeout(() => {
context.commit('setCount', payload);
}, 2000);
}
}
- アクションの呼び出しはdispatchメソッドを使用する。
methods: {
setCountAsync() {
this.$store.dispatch('setCountAsync', {count: 10});
},
},
mapActionsヘルパー関数
import { mapActions } from 'vuex'
// 〜省略〜
methods: {
...mapActions(['plusAsync']),
},
モジュール
- ストアの肥大化を防ぐために、モジュールとして分割する手段が提供されている。
- コンポーネントからステートには
this.$store.state.モジュール名.ステート名
でアクセスできる。 - コンポーネントからミューテーションには
this.$store.commit('module1/ミューテーション名')
でアクセスできる。
(モジュールにnamespaced: true
がないとグローバル名前空間に登録されてしまうので注意) - mapStateとmapMutationsはそれぞれ以下のような記述方法になる。
...mapState({ count2: state => state.module2.count })
...mapMutations({ plusForModule2: 'module2/plus' })
export default new Vuex.Store({
modules: {
module1: Module1,
module2: Module2
}
})
export default{
namespaced: true,
state: {
count: 0
},
mutations: {
plus(state) {
state.count++;
}
},
}
<template>
<div>
<p>{{ count1 }}</p>
<p>{{ count2 }}</p>
<button @click="plusForModule1">+</button>
<button @click="plusForModule2">+</button>
</div>
</template>
<script>
import { mapState, mapMutations } from 'vuex'
export default {
name: 'App',
computed: {
count1() {
return this.$store.state.module1.count;
},
...mapState({
count2: state => state.module2.count
})
},
methods: {
plusForModule1() {
this.$store.commit('module1/plus');
},
...mapMutations({
plusForModule2: 'module2/plus'
})
}
}
</script>