Author: 水卜
Author: 水卜
vuex
vueアプリのステートパターンライブラリ
Vueインスタンスにstoreと書くと、全ての子コンポーネントにstoreが注入される。
Vue.use(vuex);
const app = new Vue({
el: '#app',
// "store" オプションで指定されたストアは、全ての子コンポーネントに注入されます
store,
components: { Counter },
template: `
<div class="app">
<counter></counter>
</div>
`
});
まとめ
- storeに値を保存する時はAction(非同期)
- storeを変更する時はMutation(同期)
- storeから値を取り出すときはgetter
- storeがでかくなってきたらmodule単位で切る
以下、公式ドキュメントを参考に使い方まとめ
https://vuex.vuejs.org/ja/guide/actions.html
storeの値を取得する
注入された子コンポーネントからはthis.$storeでアクセスできる。
const Counter = {
template: `<div>{{ count }}</div>`,
computed: {
count () {
return this.$store.state.count
}
}
}
mapState
mapStateを使って算出プロパティに複数のステートを展開できる。
import { mapState } from 'vuex'
export default {
// ...
computed: mapState({
count: state => state.count,
// 文字列を渡すことは、`state => state.count` と同じです
countAlias: 'count',
// `this` からローカルステートを参照するときは、通常の関数を使わなければいけません
countPlusLocalState (state) {
return state.count + this.localCount
}
})
}
ローカルのcomputedと混ぜたい時は…mapStateでオブジェクトを展開し、混ぜる。
computed: {
localComputed () { /* ... */ },
// オブジェクトスプレット演算子で、外のオブジェクトとこのオブジェクトを混ぜる
...mapState({
// ...
})
}
getter
storeにはgettersを設定できる。
算出プロパティにロジックをかくと複数のコンポーネントで共有できないが、storeのgetterにしてしまえば全てのコンポーネントが利用できる。
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)
},
// プロパティスタイルアクセス
doneTodosCount: (state, getters) => {
return getters.doneTodos.length
},
// 引数も渡せる
getTodoById: (state) => (id) => {
return state.todos.find(todo => todo.id === id)
}
}
})
store.getters.doneTodosCount // -> 1
store.getters.getTodoById(2) // -> { id: 2, text: '...', done: false }
mapGetters
import { mapGetters } from 'vuex'
export default {
// ...
computed: {
// ゲッターを、スプレッド演算子(object spread operator)を使って computed に組み込む
...mapGetters([
'doneTodosCount',
'anotherGetter',
// ...
])
}
}
ミューテーション
storeを変更する時はミューテーションをコミットする。
ミューテーションには引数も渡せる。
普通はpayloadオブジェクトに引数を持たせてcommitする。
const store = new Vuex.Store({
state: {
count: 1
},
mutations: {
increment (state, payload) {
state.count += payload.amount
}
add (state, n) {
state.count += n
}
}
})
store.commit('increment', {
amount: 10
});
store.commit('add', 10)
新しいプロパティを追加する
以下のいずれかを使用する。
Vue.set(obj, 'newProp', 123)
state.obj = { ...state.obj, newProp: 123 }
定数をmutationの関数名にする
// store.js
import Vuex from 'vuex'
import { SOME_MUTATION } from './mutation-types'
const store = new Vuex.Store({
state: { ... },
mutations: {
[SOME_MUTATION] (state) {
// 状態を変更する
}
}
})
ミューテーションは同期的でなければならない
非同期処理が必要な時はactionを使う。
アクション
storeに値を保存する時、非同期処理が必要な時はactionで行う。
actionは第一引数にcommit, state, rootStateを内包したcontextをとり、第二引数にpayloadを渡せる。
commitはmutationをコミットするためのもの
stateはローカルステート
rootStateは、そのactionsがmodule内に作成されているとき、その呼び出し元のstateが格納されている。
action (context: {commit, state, rootState}, payload: {}) {}
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment (state) {
state.count++
}
},
actions: {
increment (context) {
context.commit('increment')
}
}
})
以下のように書くとcontext.commitではなくcommitで済む
actions: {
increment ({ commit }) {
commit('increment')
}
}
dispatch
Actionはdispatchで実行する
store.dispatch('increment')
// ペイロードを渡す例
store.dispatch('incrementAsync', {
amount: 10
})
実践例
actions: {
checkout ({ commit, state }, products) {
// 現在のカート内の商品を保存する
const savedCartItems = [...state.cart.added]
// チェックアウトのリクエストを送信し、楽観的にカート内をクリアする
commit(types.CHECKOUT_REQUEST)
// shop API は成功時のコールバックと失敗時のコールバックを受け取る
shop.buyProducts(
products,
// 成功時の処理
() => commit(types.CHECKOUT_SUCCESS),
// 失敗時の処理
() => commit(types.CHECKOUT_FAILURE, savedCartItems)
)
}
}
actions: {
actionA ({ commit }) {
return new Promise((resolve, reject) => {
setTimeout(() => {
commit('someMutation')
resolve()
}, 1000)
})
}
}
mapActions
コンポーネントからActionを呼ぶ時はthis.$store.dispatch('')
を呼ぶ。
methodsのなかに...mapActions('increment')
を書くと、this.increment()
にthis.$store.dispatch('increment')
をマッピングする。
簡潔に書けるため普通はこちらを使ってActionを呼び出す。
import { mapActions } from 'vuex'
export default {
// ...
methods: {
...mapActions([
'increment', // `this.increment()` を `this.$store.dispatch('increment')` にマッピングする
// `mapActions` もペイロードをサポートする:
'incrementBy' // `this.incrementBy(amount)` を `this.$store.dispatch('incrementBy', amount)` にマッピングする
]),
...mapActions({
add: 'increment' // `this.add()` を `this.$store.dispatch('increment')` にマッピングする
})
}
}
非同期Actionの扱い方
store.dispatchはPrimiseを返すので、thenで拾える。
actions: {
actionA ({ commit }) {
return new Promise((resolve, reject) => {
setTimeout(() => {
commit('someMutation')
resolve()
}, 1000)
})
}
}
store.dispatch('actionA').then(() => {
// actionが完了したらやりたいこと
})
Action同士が呼び合う場合も、Actionを非同期関数としてawaitで終了を待つことができる。
actions: {
async actionA ({ commit }) {
commit('gotData', await getData())
},
async actionB ({ dispatch, commit }) {
await dispatch('actionA') // `actionA` が完了するのを待機する
commit('gotOtherData', await getOtherData())
}
}
storeをmoduleに分割
storeはモジュールに分割できる。
モジュールはそれぞれstate, mutation, action, getter, moduleを持つことができる。
moduleをネストさせることもできる
const moduleA = {
state: { ... },
mutations: { ... },
actions: { ... },
getters: { ... }
}
const moduleB = {
state: { ... },
mutations: { ... },
actions: { ... }
}
const store = new Vuex.Store({
modules: {
a: moduleA,
b: moduleB
}
})
store.state.a // -> `moduleA` のステート
store.state.b // -> `moduleB` のステート
ローカルステート
moduleのgetterやmutationsに渡される第一引数のstateは、そのmoduleのローカルステートをさす。
const moduleA = {
state: { count: 0 },
mutations: {
increment (state) {
state.count++
}
},
getters: {
doubleCount (state) {
return state.count * 2
}
}
}
ルートステート
actionsからはcontext.rootStateでアクセスできる
getterからは第三引数でアクセスできる
actions
actions: {
incrementIfOddOnRootSum ({ state, commit, rootState }) {
if ((state.count + rootState.count) % 2 === 1) {
commit('increment')
}
}
}
getters
const moduleA = {
// ...
getters: {
sumWithRootCount (state, getters, rootState) {
return state.count + rootState.count
}
}
}
名前空間
ルートモジュールでnamespaced: trueに設定するとモジュールを名前空間に分けられる。
以下の例ではaccount/isAdmin, account/posts/popularのように区切ることを指す。
詳細は公式まで
https://vuex.vuejs.org/ja/guide/modules.html
const store = new Vuex.Store({
modules: {
account: {
namespaced: true,
// モジュールのアセット
state: { ... }, // モジュールステートはすでにネストされており、名前空間のオプションによって影響を受けません
getters: {
isAdmin () { ... } // -> getters['account/isAdmin']
},
actions: {
login () { ... } // -> dispatch('account/login')
},
mutations: {
login () { ... } // -> commit('account/login')
},
// ネストされたモジュール
modules: {
// 親モジュールから名前空間を継承する
myPage: {
state: { ... },
getters: {
profile () { ... } // -> getters['account/profile']
}
},
// さらに名前空間をネストする
posts: {
namespaced: true,
state: { ... },
getters: {
popular () { ... } // -> getters['account/posts/popular']
}
}
}
}
}
})