음악, 삶, 개발
모든 Component 들간에 하나의 저장소 : Vuex 본문
< 공식 문서 >
Vuex 4.0 공식문서
< Vuex 란? : Global State >
아래와 같이 중첩된 요소들이 있다고 가정해보자.
<parent>
<child>
<grand-child>
<grand-grand-child>
<grand-grand-grand-child />
</grand-grand-child>
</grand-child>
</child>
</parent>
우리가 만들 app 의 모습도 대략 이럴것이다.
이때 서로 데이터를 주고 받는 것을 생각해보자.
가장 부모인 parent 에서 가장 아래 자식인 grand-grand-grand-child 로 데이터를 전송해야한다면..
parent -> child -> grand-child -> grand-grand-child -> grand-grand-child
순으로 props 를 넘겨가야한다.
역으로, 자식 -> 부모로 데이터를 전달하려면 emit 함수를 통해 event 를 보내야한다.
아래와 같은 상황도 고려해보자.
<parent>
<brother/>
<sister>
<sister-child/>
</sister>
</parent>
brother 에서 sister-child 에게 데이터를 넘겨야한다면?
이둘은 부모-자식 관계가 아니다.
이때는 eventBus 를 사용해야한다.
이렇게 Vue 의 컴포넌트간에 데이터 주고받기가 조금 까다로운 이유는,
각 컴포넌트의 state 들이 local scope, 즉 local variable 같은 녀석들이기때문이다.
나의 컴포넌트가 1000개, 10000개가 되어간다면,
서로간에 데이터 주고받기는 더더욱 고통스러워질것이다.
이를 극복하기위해 나온것이 Vuex 이다.
Vuex 는 Global State 를 제공하는 npm 라이브러리이다.
Vue 팀에서 직접 만들어 유지보수하고있기때문에, 믿고 사용할수있다.
Vuex 를 사용하면 각 컴포넌트의 state 를 해당 컴포넌트의 local 에서 관리하는 대신,
Global 로써 상태를 저장하고, 이 상태가 변화했을때 컴포넌트가 다시 랜더링된다.
(Juce 의 ValueTree 같은 녀석이다)
< 설치방법 >
npm install vuex@4.0.0-rc.2
현재 release candidate 상태라, @버전명을 붙여 위와 같이 작성한다.
추후 정식으로 release 되면 npm install vuex 로 설치가능할것이다.
반드시 vuex 3 아닌 vuex 4 를 설치해야한다.
vuex 4 가 vue 3 와 호환되기때문이다. (composition api 관련)
< 사용법 >
/* store.js */
import { createStore } from 'vuex'
const store = createStore({
state : { count : 0},
mutations : {
increment(state) { state.count++ },
decrement(state) { state.count-- },
}
})
export default store
또는,
/* store.js */
import { createStore } from 'vuex'
const state = { count : 0 }
const mutations = {
increment(state) { state.count++ },
decrement(state) { state.count-- },
}
const store = createStore({ state, mutations })
export default store
/* main.js */
import { createApp } from 'vue'
import { store } from './store/store.js'
import App from './App.vue'
const app = createApp(App)
app.use(store)
app.mount('#app')
<!-- App.vue -->
<template>
{{ store.state.count }}
</template>
<script>
import { useStore } from 'vuex'
export default {
setup() {
const store = useStore()
return { store }
}
}
</script>
< 예제 코드 >
/* store.js */
import { createStore } from 'vuex'
const state = {
count : 1
}
const getters = {
nextCount (state) { return state.count + 1 },
nextNextCount (state, getters) { return getters.nextCount + 1},
getSumCount : (state) => (numberToSum) => { return state.count + numberToSum }
}
const mutations = {
increment (state) { state.count++ },
decrement (state) { state.count-- },
add (state, payload) { state.count += payload.amount },
sub (state, payload) { state.count -= payload.amount }
}
const actions = {
increment(context) { context.commit('increment') }
}
const store = createStore({
state : state,
mutations : mutations,
getters : getters,
actions : actions
})
export default store
/* main.js */
import { createApp } from 'vue'
import { store } from './store/store.js'
import App from './App.vue'
const app = createApp(App)
app.use(store)
app.mount('#app')
<!-- App.vue -->
<template>
<!-- state -->
<div>{{ store.state.count }} </div>
<!-- getters -->
<div>{{ store.getters.nextCount }} </div>
<div>{{ store.getters.nextNextCount }} </div>
<div>{{ store.getters.getSumCount(100)}} </div>
<!-- mutations (sync) -->
<button @mousedown="store.commit('increment')">inc</button>
<button @mousedown="store.commit('decrement')">dec</button>
<button @mousedown="store.commit('add', { amount : 12})">+12</button>
<button @mousedown="store.commit({ type : 'sub', amount : 12})">-12</button>
<!-- actions (async) -->
<button @mousedown="store.dispatch('increment')">inc (async)</button>
<!-- computed from 'vue' -->
<div> {{ randomCount }} </div>
</template>
<script>
import { useStore } from 'vuex'
import { computed } from 'vue'
export default {
setup() {
const store = useStore()
const randomCount = computed(() => store.state.count + Math.random())
return { store, randomCount }
}
}
</script>
순살.
< 최종 정리 >
/* store.js */
import { createStore } from 'vuex'
const store = createStore({
state : {},
mutations : {},
getters : {},
actions : {}
})
export default store
<!-- App.vue -->
<script>
import { computed } from 'vue'
import { useStore } from 'vuex'
export default {
setup () {
// access a store
const store = useStore()
return {
// access a state in computed function
count: computed(() => store.state.count),
// access a getter in computed function
double: computed(() => store.getters.double),
// access a mutation
increment: () => store.commit('increment'),
// access an action
asyncIncrement: () => store.dispatch('asyncIncrement')
}
}
}
</script>
< Strict Mode >
디버깅을 원할하게 하기위해 Strict Mode 를 지원한다.
const store = createStore({
strict: true
})
위와 같이 strict 이 true 가 되면,
Vuex state 가 mutation 함수가 아닌 다른것에 의해 변경되었을때, error 를 던진다고한다.
주의할점은 strict mode 는 매우 비싸기때문에, 개발할때만 사용해야하며,
build 할때는 이 옵션을 반드시 꺼야한다.
< 의문점 : 그럼 모든 컴포넌트 의 state 를 Vuex 로? >
다행히, 이 질문에 대한 대답이 공식문서에 나와있다.
이중, 가장 중요한 부분은...
If a piece of state strictly belongs to a single component,
it could be just fine leaving it as local state.
만약 해당 컴포넌트의 데이터가 다른곳에서 절대 쓰일것이 아니라면,
local state 로 가지고있는것이 맞다.