하나의 스토어에서 모든 state를 관리하는 것이 vuex이다.
하지만 애플리케이션의 규모가 커짐에 따라 하나의 스토어에 너무 많은 것들이 들어가는 문제가 생긴다.
그래서 스토어를 여러 개의 modules
로 나눌 수 있도록 하였다.
각 모듈은 마찬가지로 state, mutations, getters, actions를 가질 수 있으며, 심지어는 그 안에 또다른 module을 가질 수도 있다.
아래 vuex에서 제공하는 예제를 보면, moduleA와 moduleB가 있고, 이를 store에 modules
라는 프로퍼티에 추가한 것을 확인할 수 있다.
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`'s state
store.state.b // -> `moduleB`'s state
mutation과 getters에서 첫 번째 인자로 모듈의 local state를 받는다.
const moduleA = {
state: () => ({
count: 0
}),
mutations: {
increment (state) {
// `state` is the local module state
state.count++
}
},
getters: {
doubleCount (state) {
return state.count * 2
}
}
}
module actions에서 첫 번째로 인자로 context
를 받는다.
이 때, context.state
가 모듈의 local state이다.
context.rootState
로 root state에 접근할 수 있다.
const moduleA = {
// ...
actions: {
incrementIfOddOnRootSum ({ state, commit, rootState }) {
if ((state.count + rootState.count) % 2 === 1) {
commit('increment')
}
}
}
}
getters에서 세 번째 인자로 rootState를 받을 수 있다.
const moduleA = {
// ...
getters: {
sumWithRootCount (state, getters, rootState) {
return state.count + rootState.count
}
}
}
기본적으로 action과 mutation은 전역 namespace에 등록된다.
이를 통해 여러 모듈이 같은 action, mutation 타입에 반응할 수 있게 된다.
Getters 또한 전역 namespace에 등록된다. 그러나 namespace되어있지 않은 서로 다른 모듈에서 같은 이름의 getter를 사용하면 에러가 발생한다.
namespace 만들기
모듈이 좀 더 selft-contained되거나 Reusable되기를 원한다면, namespaced:true
를 추가하면 된다. 이는 getters, actions, mutations이 자동으로 모듈이 등록된 경로에 namespaced된다는 것을 의미한다.
namespaced된 getters와 actions는 localized getters, dispatch, commit을 받는다. 따라서, 같은 모듈에 있는 것을 사용할 때는 prefix가 필요없다.
모듈 안에 모듈을 만들 때는, 중첩된 모듈 안에서는 부모 모듈의 namespace를 상속받는다. 즉 중첩된 모듈의 namespace는 parent
이다.
const store = new Vuex.Store({
modules: {
account: {
namespaced: true,
// module assets
state: () => ({ ... }), // module state is already nested and not affected by namespace option
getters: {
isAdmin () { ... } // -> getters['account/isAdmin']
},
actions: {
login () { ... } // -> dispatch('account/login')
},
mutations: {
login () { ... } // -> commit('account/login')
},
// nested modules
modules: {
// inherits the namespace from parent module
myPage: {
state: () => ({ ... }),
getters: {
profile () { ... } // -> getters['account/profile']
}
},
// further nest the namespace
posts: {
namespaced: true,
state: () => ({ ... }),
getters: {
popular () { ... } // -> getters['account/posts/popular']
}
}
}
}
}
})
rootState
, rootGetters
가 getter 함수의 3, 4번째 인자로 전달된다.
또한 같은 프로퍼티가 action 함수의 context
객체의 프로퍼티로 전달된다.
전역 namespace의 액션을 dispatch하거나, mutation을 commit할 때는 {root:true}
를 distpatch
와 commit
의 세 번째 인자로 전달하면 된다.
modules: {
foo: {
namespaced: true,
getters: {
// `getters` is localized to this module's getters
// you can use rootGetters via 4th argument of getters
someGetter (state, getters, rootState, rootGetters) {
getters.someOtherGetter // -> 'foo/someOtherGetter'
rootGetters.someOtherGetter // -> 'someOtherGetter'
rootGetters['bar/someOtherGetter'] // -> 'bar/someOtherGetter'
},
someOtherGetter: state => { ... }
},
actions: {
// dispatch and commit are also localized for this module
// they will accept `root` option for the root dispatch/commit
someAction ({ dispatch, commit, getters, rootGetters }) {
getters.someGetter // -> 'foo/someGetter'
rootGetters.someGetter // -> 'someGetter'
rootGetters['bar/someGetter'] // -> 'bar/someGetter'
dispatch('someOtherAction') // -> 'foo/someOtherAction'
dispatch('someOtherAction', null, { root: true }) // -> 'someOtherAction'
commit('someMutation') // -> 'foo/someMutation'
commit('someMutation', null, { root: true }) // -> 'someMutation'
},
someOtherAction (ctx, payload) { ... }
}
}
}
root:true
를 추가하고, 액션의 정의를 handler
함수에 두면 된다.
{
actions: {
someOtherAction ({dispatch}) {
dispatch('someAction')
}
},
modules: {
foo: {
namespaced: true,
actions: {
someAction: {
root: true,
handler (namespacedContext, payload) { ... } // -> 'someAction'
}
}
}
}
}
namespaced module을 컴포넌트에서 mapState, mapGetters와 같은 helper에 바인딩하려고 할 때, 다소 이름이 길어지는 문제가 생긴다.
...mapGetters([
'some/nested/module/someGetter', // -> this['some/nested/module/someGetter']
'some/nested/module/someOtherGetter', // -> this['some/nested/module/someOtherGetter']
])
짧게 만들기 위해서 helper의 첫 번째 인자에 모듈 namespace를 전달하여, 해당 모듈에 바인드되도록 할 수 있다.
computed: {
...mapState('some/nested/module', {
a: state => state.a,
b: state => state.b
}),
...mapGetters('some/nested/module', [
'someGetter', // -> this.someGetter
'someOtherGetter', // -> this.someOtherGetter
])
},
methods: {
...mapActions('some/nested/module', [
'foo', // -> this.foo()
'bar' // -> this.bar()
])
}
또 다른 방법으로는 createNamespacedHelpers
를 사용하여 namespaced helper를 사용하는 것이다. 이것은 namespace로 바인드된 helper 객체를 반환한다.
사용법은 createNamespacedHelpers
에 namespace이름을 넣고, 반환되는 helper 객체를 사용하는 것이다.
import { createNamespacedHelpers } from 'vuex'
const { mapState, mapActions } = createNamespacedHelpers('some/nested/module')
export default {
computed: {
// look up in `some/nested/module`
...mapState({
a: state => state.a,
b: state => state.b
})
},
methods: {
// look up in `some/nested/module`
...mapActions([
'foo',
'bar'
])
}
}
You may care about unpredictable namespacing for your modules when you create a plugin that provides the modules and let users add them to a Vuex store. Your modules will be also namespaced if the plugin users add your modules under a namespaced module. To adapt this situation, you may need to receive a namespace value via your plugin option:
플러그인 만들 때, namespace를 옵션으로 받을 수 있게한다는 것 같은데,
플러그인이 뭔지 모르겠어서 아직은 이해가 안간다. -> Advanced에 있는 플러그인 공부해야겠다.
// get namespace value via plugin option
// and returns Vuex plugin function
export function createPlugin (options = {}) {
return function (store) {
// add namespace to plugin module's types
const namespace = options.namespace || ''
store.dispatch(namespace + 'pluginAction')
}
}
기존에는 모듈을 만들고, 해당 모듈을 스토어에 추가하였다.
동적 모듈 할당이란, store.registerModule
메소드로 스토어가 생성된 후에 모듈을 등록하는 것을 말한다.
import Vuex from 'vuex'
const store = new Vuex.Store({ /* options */ })
// register a module `myModule`
store.registerModule('myModule', {
// ...
})
// register a nested module `nested/myModule`
store.registerModule(['nested', 'myModule'], {
// ...
})
이 모듈의 state는 store.state.myuModule
, store.state.nested.myModule
로 나타내어진다.
동적 모듈의 장점
동적 모듈을 등록함으로써, 다른 뷰 플러그인이 스토어에 모듈을 붙임으로써, Vuex 상태관리를 사용할 수 있게 된다.
예를 들면, vue-router-sync
라이브러리가 vue와 vue-router를 통합시켰다. 이 라이브러리는 동적으로 모듈을 붙이는 방식으로 애플리케이션의 route state를 관리한다.
동적 모듈 삭제하기
store.unregisterModule(moduleName)
로 동적 모듈을 삭제할 수도 있다. 단, 이 메소드로 정적 모듈(스토어 생성시 등록되었던 모듈)은 지우지 못한다.
모듈의 등록 여부 확인하기
store.hasModule(moduleName)
메소드로 모듈이 스토어에 이미 등록되었는지를 확인할 수 있다.
Preserving state : state 보존하기
새로운 모듈을 등록할 때, 이전 state를 보존하고 싶을 경우가 있다.
이 떄, preserveState
option으로 보존할 수 있다.
store.registerModule('a', module, { preserveState: true })
preserveState:true
로 하면, 모듈은 등록되고, action, mutation, getter가 스토어에 추가된다. 그러나, state는 아니다. 이는 store state가 이미 해당 모듈의 state를 갖고 있으며, 그것을 당신이 덮어씌우기를 원하지 않는다고 여기기 때문이다.
-> …? 뭐지…?
모듈의 여러 instance를 만들어야 하는 경우가 생긴다.
예를 들면,
만약 모듈의 state를 plain object로 정의한다면, state 객체의 reference가 공유되어, 여러 스토어를 걸쳐 state가 오염될 수 있다.
따라서, vue component에 data
를 만들 때, 함수를 반환해야하는 것처럼,
module state에 함수를 사용하고, 그 함수가 객체를 반환하게 하면 된다.
const MyReusableModule = {
state: () => ({
foo: 'bar'
}),
// mutations, actions, getters...
}