Vuex モジュール
今日のお題はこちらです。
- https://vuex.vuejs.org/ja/modules.html
- vuex のモジュール化は、最初は使わないかなと思っていたのですが、vuexを使うと、データの心臓部分なので、すぐ肥大化してきますのでモジュールは必須だと思い目を通します。
src/store.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const moduleA = {
state: { count: 1 },
mutations: {},
actions: {},
getters: {}
}
const moduleB = {
state: { count: 10 },
mutations: {},
actions: {},
getters: {}
}
const store = new Vuex.Store({
modules: {
a: moduleA,
b: moduleB
}
})
export default store
src/App.vue
<template lang="pug">
#app
.box.has-text-centered
.columns
.column
p {{ $store.state.a.count }}
p {{ $store.state.b.count }}
</template>
<script>
import Vue from 'vue'
import store from './store.js'
export default {
name: 'app',
store,
data: () => ({
privateState: {},
}),
computed: {
count () {
//-return store.state.count
}
},
methods: {
}
}
</script>
<style lang="scss">
#app {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
// text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
- モジュール化するとアクセスは、間にモジュール名を割り込ませる形でアクセスするみたいですね。
モジュールのローカルステート
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const moduleA = {
state: { count: 1 },
mutations: {
increment (state) {
state.count ++
}
},
actions: {},
getters: {
doubleCount (state) {
return state.count * 2
}
}
}
const moduleB = {
state: { count: 10 },
mutations: {},
actions: {},
getters: {}
}
const store = new Vuex.Store({
modules: {
a: moduleA,
b: moduleB
}
})
export default store
- App.vue
<template lang="pug">
#app
.box.has-text-centered
.columns
.column
p {{ $store.state.a.count }}
p {{ $store.state.b.count }}
p {{ $store.getters.doubleCount }}
</template>
<script>
import Vue from 'vue'
import store from './store.js'
export default {
name: 'app',
store,
data: () => ({
privateState: {},
}),
computed: {
count () {
//-return store.state.count
}
},
methods: {
}
}
</script>
- モジュールの第一引数はローカルのstateなるみたいですね。
-
getterへのアクセスは、defaultは,
モジュール個別では無くグローバルになっていることに注意ですね。p {{ $store.getters.doubleCount }}
store.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const moduleA = {
state: { count: 1 },
mutations: {
increment (state) {
state.count ++
console.log('increment', state.count)
}
},
actions: {
incrementIfOddOnRootSum ({ state, commit, rootState }) {
if ((state.count + rootState.count) % 2 === 1)
commit('increment')
}
},
getters: {
doubleCount (state) {
return state.count * 2
}
}
}
const moduleB = {
state: { count: 10 },
mutations: {},
actions: {},
getters: {}
}
const store = new Vuex.Store({
state: {
count: 100
},
modules: {
a: moduleA,
b: moduleB
}
})
export default store
- App.vue
<template lang="pug">
#app
.box.has-text-centered
.columns
.column
p moduleA.count: {{ $store.state.a.count }}
p moduleB.count: {{ $store.state.b.count }}
p doubleCount: {{ $store.getters.doubleCount }}
button.button.is-primary(@click="$store.dispatch('incrementIfOddOnRootSum')") moduleA action button
</template>
<script>
import Vue from 'vue'
import store from './store.js'
export default {
name: 'app',
store,
data: () => ({
privateState: {},
}),
computed: {
count () {
//-return store.state.count
}
},
methods: {
}
}
</script>
-
actionでは、contextでrootへのアクセスができるみたいですね。
- ここでは、contextの分割代入で記載されています。この形がactionsのdefaultになりそうですね。
- ボタンも正常動作しています。
store.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const moduleA = {
state: { count: 1 },
mutations: {
increment (state) {
state.count ++
console.log('increment', state.count)
}
},
actions: {
incrementIfOddOnRootSum ({ state, commit, rootState }) {
if ((state.count + rootState.count) % 2 === 1)
commit('increment')
}
},
getters: {
sumWithRootCount (state, getters, rootState) {
return state.count + rootState.count
},
doubleCount (state) {
return state.count * 2
}
}
}
const moduleB = {
state: { count: 10 },
mutations: {},
actions: {},
getters: {}
}
const store = new Vuex.Store({
state: {
count: 100
},
modules: {
a: moduleA,
b: moduleB
}
})
export default store
- App.vue
<template lang="pug">
#app
.box.has-text-centered
.columns
.column
p moduleA.count: {{ $store.state.a.count }}
p moduleB.count: {{ $store.state.b.count }}
p doubleCount: {{ $store.getters.doubleCount }}
p sumWithRootCount: {{ $store.getters.sumWithRootCount }}
button.button.is-primary(@click="$store.dispatch('incrementIfOddOnRootSum')") moduleA action button
</template>
<script>
import Vue from 'vue'
import store from './store.js'
export default {
name: 'app',
store,
data: () => ({
privateState: {},
}),
computed: {
count () {
//-return store.state.count
}
},
methods: {
}
}
</script>
- モジュールのgetter は第三引数で、rootStateにアクセス出来るとのこと。
名前空間
- store.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const moduleA = {
state: { count: 1 },
mutations: {
increment (state) {
state.count ++
console.log('increment', state.count)
}
},
actions: {
incrementIfOddOnRootSum ({ state, commit, rootState }) {
if ((state.count + rootState.count) % 2 === 1)
commit('increment')
}
},
getters: {
sumWithRootCount (state, getters, rootState) {
return state.count + rootState.count
},
doubleCount (state) {
return state.count * 2
}
}
}
const moduleB = {
state: { count: 10 },
mutations: {},
actions: {},
getters: {}
}
const account = {
namespaced: true,
state: { lastName: "Ohtani", firstName: "Syohei" },
getters: {
isAdmin (state, getters, rootState) {
return state.lastName + state.firstName
}
},
actions: {},
mutations: {},
modules: {
myPage: {
state: {},
getters: {
profile (state, getters, rootState) {
return getters.isAdmin
}
}
},
posts: {
namespaced: true,
state: { skill: "二刀流"},
getters: {
popular (state, getters, rootState) {
return state.skill
}
}
}
},
}
const store = new Vuex.Store({
state: {
count: 100
},
modules: {
a: moduleA,
b: moduleB,
account
}
})
export default store
- App.vue
<template lang="pug">
#app
.box.has-text-centered
.columns
.column
p account.state: {{ $store.state.account.lastName + $store.state.account.firstName }}
p account.getters: {{ $store.getters['account/isAdmin'] }}
p account.getters.profile: {{ $store.getters['account/profile'] }}
p account.getters.posts: {{ $store.getters['account/posts/popular'] }}
</template>
<script>
import Vue from 'vue'
import store from './store.js'
export default {
name: 'app',
store,
data: () => ({
privateState: {},
}),
computed: {
count () {
//-return store.state.count
}
},
methods: {
}
}
</script>
- 名前空間を使用すると、モジュール名を書く必要がありますので少し冗長になりますがそのかわり、似た機能を複数書く場合には、それで区別可能になりpost1, post2などの連番を振ったりgetter名の重複による意図しない動作を軽減できそうです。
- stateは、名前空間の有無に影響しないことも把握していると混乱しないかと思います。
名前空間付きモジュールでのグローバルアセットへのアクセス
- store.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const foo = {
namespaced: true,
getters: {
someGetter (state, getters, rootState, rootGetters) {
const a = getters.someOtherGetter
const b = rootGetters.someOtherGetter
return a + b
},
someOtherGetter (state, getters, rootState, rootGetters) {
return 'foo.someOtherGetter'
}
}
}
const account = {
namespaced: true,
state: { lastName: "Ohtani", firstName: "Syohei" },
getters: {
isAdmin (state, getters, rootState) {
return state.lastName + state.firstName
}
},
actions: {},
mutations: {},
modules: {
myPage: {
state: {},
getters: {
profile (state, getters, rootState) {
return getters.isAdmin
}
}
},
posts: {
namespaced: true,
state: { skill: "二刀流"},
getters: {
popular (state, getters, rootState) {
return state.skill
}
}
}
},
}
const store = new Vuex.Store({
state: {
count: 100,
some: 'ROOT STATE'
},
getters: {
someOtherGetter (state, getters) {
return state.some
}
},
modules: {
account,
foo
}
})
export default store
- App.vue
<template lang="pug">
#app
.box.has-text-centered
.columns
.column
p account.state: {{ $store.state.account.lastName + $store.state.account.firstName }}
p account.getters: {{ $store.getters['account/isAdmin'] }}
p account.getters.profile: {{ $store.getters['account/profile'] }}
p account.getters.posts: {{ $store.getters['account/posts/popular'] }}
p {{ $store.getters['foo/someGetter']}}
p {{ $store.getters['foo/someOtherGetter']}}
</template>
<script>
import Vue from 'vue'
import store from './store.js'
export default {
name: 'app',
store,
data: () => ({
privateState: {},
}),
computed: {
count () {
//-return store.state.count
}
},
methods: {
}
}
</script>
-
モジュールから、ルートへのアクセスはそれぞれ、第三引数、第四引数を使うと出来ます。
someGetter (state, getters, rootState, rootGetters)
store.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const foo = {
namespaced: true,
getters: {
someGetter (state, getters, rootState, rootGetters) {
return getters.someOtherGetter
},
someOtherGetter (state, getters, rootState, rootGetters) {
return 'foo.someOtherGetter'
},
},
actions: {
someAction ({ dispatch, commit, getters, rootGetters }) {
getters.someGetter // foo/someGetter が実行される
rootGetters.someGetter // someGetter が実行される
dispatch('someOtherAction') // foo/someOtherActionが実行される
dispatch('someOtherAction', null, { root: true }) // root のsomeOhterActionが実行される
commit('someMutation') // foo/someMutation が実行される
commit('someMutation', null, { root: true }) // rootのsomeMutationが実行される
},
someOtherAction (ctx, payload) {}
}
}
const account = {
namespaced: true,
state: { lastName: "Ohtani", firstName: "Syohei" },
getters: {
isAdmin (state, getters, rootState) {
return state.lastName + state.firstName
}
},
actions: {},
mutations: {},
modules: {
myPage: {
state: {},
getters: {
profile (state, getters, rootState) {
return getters.isAdmin
}
}
},
posts: {
namespaced: true,
state: { skill: "二刀流"},
getters: {
popular (state, getters, rootState) {
return state.skill
}
}
}
},
}
const store = new Vuex.Store({
state: {
count: 100,
some: 'ROOT STATE'
},
getters: {
someOtherGetter (state, getters) {
return state.some
}
},
modules: {
account,
foo
}
})
export default store
- モジュールのアクションは、オプションで
root: true
で dispatch と commit を中で使う場合切り替えるみたいです。 - こうみると、アクションは、特に制限なくどこにでもアクセス出来る事がわかりますね。
名前空間によるバインディングヘルパー
- store.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const foo = {
namespaced: true,
state: {
count: '0'
},
mutations: {
incrementFoo (state) {
state.count ++
}
},
getters: {
someGetter (state, getters, rootState, rootGetters) {
return getters.someOtherGetter
},
someOtherGetter (state, getters, rootState, rootGetters) {
return 'foo.someOtherGetter'
},
},
actions: {
incrementFoo ({ commit }) {
commit('incrementFoo')
},
someAction ({ dispatch, commit, getters, rootGetters }) {
getters.someGetter // foo/someGetter が実行される
rootGetters.someGetter // someGetter が実行される
dispatch('someOtherAction') // foo/someOtherActionが実行される
dispatch('someOtherAction', null, { root: true }) // root のsomeOhterActionが実行される
commit('someMutation') // foo/someMutation が実行される
commit('someMutation', null, { root: true }) // rootのsomeMutationが実行される
},
someOtherAction (ctx, payload) {}
}
}
const store = new Vuex.Store({
state: {
count: 100,
some: 'ROOT STATE'
},
getters: {
someOtherGetter (state, getters) {
return state.some
}
},
modules: {
account,
foo
}
})
export default store
- App.vue
<template lang="pug">
#app
.box.has-text-centered
.columns
.column
p {{ a }}
button.button.is-primary(@click="incrementFoo") button mapActions
button.button.is-info(@click="$store.dispatch('foo/incrementFoo')") button
</template>
<script>
import Vue from 'vue'
import store from './store.js'
import { mapState, mapActions } from 'vuex'
export default {
name: 'app',
store,
data: () => ({
privateState: {},
}),
computed: {
...mapState({
a: state => state.foo.count
}),
},
methods: {
...mapActions({
incrementFoo: 'foo/incrementFoo',
}),
}
}
</script>
<style lang="scss">
#app {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
// text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
ヘルパーを介するとすっきりしてきました。名前空間をここで吸収する感じです。
App.js
<template lang="pug">
#app
.box.has-text-centered
.columns
.column
p {{ a }}
button.button.is-primary(@click="incrementFoo") button mapActions
button.button.is-info(@click="$store.dispatch('foo/incrementFoo')") button
</template>
<script>
import Vue from 'vue'
import store from './store.js'
import { mapState, mapActions } from 'vuex'
export default {
name: 'app',
store,
data: () => ({
privateState: {},
}),
computed: {
...mapState('foo', {
a: state => state.count
}),
},
methods: {
...mapActions('foo', {
incrementFoo: 'incrementFoo',
}),
}
}
</script>
- さらに、第一引数に名前空間を移せば、式は簡略化出来るとのこと、式が増えてくるとありがたいですね。
-
createNamespacedHelpers
は、ヘルパーをネームで分ける感じでしょうか? これのありがたみがいまいちわかりませんが呼び込みが増える感じがしますが、どうなんでしょう。
プラグイン開発者向けの注意事項
- 飛ばします
動的にモジュールを登録する
- 飛ばします
モジュールの再利用
- これは、stateを関数化させるだけなので、いいですね。
常に関数化させておくのがいいように思います。
モジュール編はこれで終了です。
名前空間は通常 falseになっていますが、使う方がメリットが多いので使っていこうかと思いました。 グローバルに使えると一見便利そうですが、var変数と同じニオイがします。