실제로 Vuex는 코드 구조를 제한하지는 않는다. 하지만,
위 규칙은 따르는 것이 좋다. 위 규칙대로 프로젝트를 구조화한다면,
저장소 파일이 너무 커졌을 때 action, mutation, getter를 개별 파일로 분할하기 수월하다.
├── index.html
├── main.js
├── api
│ └── ... # API 요청을 위한 추상화 포함
├── components
│ ├── App.vue
│ └── ...
└── store
├── index.js # 모듈을 조합하고 저장소를 내보내는 곳
├── actions.js # 루트 액션
├── mutations.js # 루트 변이
└── modules
├── cart.js # cart 모듈
└── products.js # products 모듈
Vuex 저장소(Store)는 각 mutation에 대한 훅을 노출하는 plugins 옵션을 허용한다.
Vuex 플러그인은 저장소(Store)를 유일한 전달인자로 받는 함수이다.
const myPlugin = store => {
// 저장소 초기화 될 때 사용
store.subscribe((mutation, state) => {
// mutation할 때마다 호출
// mutation은 { type, payload } 포맷으로 제공
})
}
그리고 다음과 같이 사용할 수 있다.
const store = new Vuex.Store({
// ...
plugins: [ myPlugin ]
})
플러그인은 상태를 직접 mutation할 수 없다.
컴포넌트와 마찬가지로 mutation을 commit하여 변경을 트리거한다.
mutation를 커밋함으로써 플러그인을 사용하여 데이터 소스를 저장소(Store)에 동기화할 수 있다.
export default function createWebSocketPlugin (socket) {
return store => {
socket.on('data', data => {
store.commit('receiveData', data)
})
store.subscribe(mutation => {
if (mutation.type === 'UPDATE_DATA') {
socket.emit('update', mutation.payload)
}
})
}
}
const plugin = createWebSocketPlugin(socket)
const store = new Vuex.Store({
state,
mutations,
plugins: [plugin]
})
때로는 플러그인이 상태의 스냅샷(어떠한 시점의 데이터)이나
mutations 이후 상태와 mutations 이전 상태의 비교가 필요하다.
이를 위해서는 상태 객체에 대한 깊은 복사를 수행해야 한다.
const myPluginWithSnapshot = store => {
let prevState = _.cloneDeep(store.state)
store.subscribe((mutation, state) => {
let nextState = _.cloneDeep(state)
// prevState와 nextState 비교
// 다음 변이를 위한 상태 저장
prevState = nextState
})
}
상태 스냅 샷을 사용하는 플러그인은 개발 중에만 사용해야 한다.
빌드 도구(ex. Browserify)가 process.env.NODE_ENV !== 'production'
의 값을 최종 빌드를 위해 false로 변환하여 이을 처리하도록 할 수 있다.
const store = new Vuex.Store({
// ...
plugins: process.env.NODE_ENV !== 'production'
? [myPluginWithSnapshot]
: []
})
strict 모드를 사용하려면 Vuex 저장소에 strict: true
를 추가하면 된다.
const store = new Vuex.Store({
// ...
strict: true
})
Strict 모드에서는 Vuex 상태가 mutation 핸들러 외부에서 mutation을 하는 부적절한 mutation이 발생할 때 마다 오류가 발생시킨다.
이렇게하면 디버깅 도구로 모든 상태 mutation을 명시적으로 추적할 수 있다.
Strict 모드는 부적절한 mutation를 감지하기 위해 상태 트리를 자세히 관찰한다.
성능 이슈를 피하기 위해 배포 환경에서는 strict 모드를 켜지않아야 한다.
플러그인과 마찬가지로 빌드 도구(ex. Browserify)가 process.env.NODE_ENV !== 'production'
의 값을 최종 빌드를 위해 false로 변환하여 이을 처리하도록 할 수 있다.
const store = new Vuex.Store({
// ...
strict: process.env.NODE_ENV !== 'production'
})
Strict 모드로 Vuex를 사용하는 경우,
Vuex에 포함된 부분에 v-model
을 사용하는 것은 약간 까다로울 수 있다.
<input v-model="obj.message">
위 소스에서 obj가 저장소(Store)에서 객체를 반환하는 computed
속성이라면,
v-model
은 사용자가 입력 할 때 obj.message
를 직접 변경하려고 한다.
하지만, Strict 모드에서는 mutation 핸들러 외부에서 mutation을 하는 부적절한 mutation이 발생하면 이를 수행되지 않고 오류가 발생시킨다.
이를 해결하는 Vuex 방식은 <input>
의 값을 바인딩하고
input 또는 change 이벤트에 대한 action을 호출하는 것이다.
<input :value="message" @input="updateMessage">
// ...
computed: {
...mapState({
message: state => state.obj.message
})
},
methods: {
updateMessage (e) {
this.$store.commit('updateMessage', e.target.value)
}
}
// ...
mutations: {
updateMessage (state, message) {
state.obj.message = message
}
}
앞서 다룬 내용으로 작업을 하게 되면 v-model + 지역 상태
보다 더 장황해지고
v-model
의 유용한 기능 중 일부를 잃어 버리게 된다.
v-model
을 사용하여 폼 핸들링을 하는 또 다른 방법은 setter를 이용하여 양방향 computed
속성을 사용하는 것이다.
<input v-model="message">
// ...
computed: {
message: {
get () {
return this.$store.state.obj.message
},
set (value) {
this.$store.commit('updateMessage', value)
}
}
}