💡 Vuex는 Vue.js 애플리케이션에 대한 상태 관리 패턴 + 라이브러리 입니다.
Vue에서 데이터 전달하는 방법 중 하나는 바로 props와 emit을 이용하는 방법입니다.
이를 이용한 방법은 연관관계가 뚜렷하기 때문에 데이터의 전달 흐름을 잘 파악하기가 쉬운 장점이 있는데요.
다만 컴포넌트의 깊이에 따라 가상 최상위 컴포넌트부터 가장 최하위 컴포넌트까지 props를 전달하기엔 비효율적인 단점이 있습니다.
또 다른 방법은 provide와 inject를 이용하는 방법 입니다.
이 방법은 직계 부모보다 더 상위의 부모로부터도 데이터 주입 받을 수 있습니다.
그러나 데이터 흐름을 직관적으로 알 수 없다는 단점을 가지고 있습니다.
또한, 일반 어플리케이션 코드에서 사용하지 않는 것을 권장하며 보통 UI라이브러리의 컴포넌트를 설계할 때 props로(혹은 문서에) 드러내고 싶지 않은 특수한 케이스의 데이터 흐름을 정의할때 사용하기도 합니다.
위에서 살펴본 두가지의 장,단점을 모두 합친것이 바로 Vuex 입니다!
이런 문제점을 해결하기 위해 모든 데이터 통신을 한 곳에서 중앙 집중식으로 관리합니다.
Vuex는 state
, mutations
, actions
, getters
4가지 형태로 관리가 됩니다.
서로간 간접적으로 영향을 받으며 단방향 데이터 흐름으로 볼 수 있어 데이터의 흐름을 파악하기가 쉽습니다.
- Vue 컴포넌트는 상태를 검색할 때 저장소의 상태가 변경되면 효율적으로 대응하고 업데이트합니다.
- 저장소의 상태를 직접 변경할 수 없습니다. 저장소의 상태를 변경하는 유일한 방법은 명시적인 커밋을 이용한 변이 입니다. 이렇게하면 모든 상태에 대한 추적이 가능한 기록이 남을 수 있으며 툴을 사용하여 앱을 더 잘 이해할 수 있습니다.
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment (state) {
state.count++
}
}
})
state 객체에 store.state
로 접근하여 store.commit
메소드로 상태 변경을 트리거 할 수 있습니다.
store.commit('increment')
console.log(store.state.count) // -> 1
다시 말해, store.state.count
를 직접 변경하는 대신 mutations
를 수행하는 이유는 상태 변화를 지켜볼 수 있을뿐 더러 시간 흐름에 따라 디버깅을 할 수 있는 도구를 제공하기 때문에 state로 직접 변경하는걸 권장하지 않습니다.
💡 원본 소스의 역할을 하는 State
말 그대로 원본 소스, 즉 Vue 컴포넌트에서 data로 볼 수 있는데요.
이 state를 통해 직접적으로 접근이 가능하더라도, 다음에 설명 드릴 getter
를 통해 접근을 하도록 합니다.
앞서 말했듯이 직접적인 변경이 아닌 mutation
을 통해서만 변경이 가능합니다. 이 mutation
을 통해 state가 변경이 일어나면 반응적으로 View 또한 업데이트 됩니다.
💡 자주 처리해야 하는 state의 데이터를 캐시를 통해 코드의 반복을 줄여줍니다.
store의 state가 필요할 때에 state에 직접 접근하는 것이 아닌 getter
를 이용합니다.
Getters는 첫 번째 전달인자로 상태를 받습니다.
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)
}
}
})
💡
getters
는store.getters
객체에 노출되고, 속성으로 값에 접근할 수 있습니다:
store.getters.doneTodos
// -> [{ id: 1, text: '...', done: true }]
Getters는 두 번째 전달인자로 다른 getter도 받게됩니다.
getters: {
// ...
doneTodosCount: (state, getters) => {
return getters.doneTodos.length
}
}
store.getters.doneTodosCount // -> 1
이제 모든 컴포넌트에서 쉽게 사용할 수 있습니다.
computed: {
doneTodosCount () {
return this.$store.getters.doneTodosCount
}
}
함수를 반환하여 getter에 전달인자로 전달할 수도 있습니다.
getters: {
// ...
getTodoById: (state) => (id) => {
return state.todos.find(todo => todo.id === id)
}
}`
`store.getters.getTodoById(2)
// -> { id: 2, text: '...', done: false }
메서드를 통해 접근하는 getter는 호출 할 때마다 실행되며 결과가 캐시되지 않는다는 것을 유의해야 합니다
💡 Vuex 저장소에서 실제로 상태를 변경하는 유일한 방법은 Mutations 하는 것입니다.
Mutations는 이벤트와 매우 유사합니다. 핸들러 함수는 실제 상태 수정을 하는 곳이며, 첫 번째 전달인자로 상태를받습니다.
const store = new Vuex.Store({
state: {
count: 1
},
mutations: {
increment (state) {
// 상태 변이
state.count++
}
}
})
mutations를 직접 호출 할 수는 없습니다. 이 옵션은 이벤트 등록과 비슷합니다. 타입이 increment인 mutations가 발생하면 이 핸들러를 호출합니다.
mutations 핸들러를 호출하려면 해당 타입과 함께 store.commit
을 호출해야합니다.
store.commit('increment')
변이에 대해 payload 라고하는 store.commit에 추가 전달인자를 사용 할 수 있습니다.
// ...
mutations: {
increment (state, n) {
state.count += n
}
}
store.commit('increment', 10)
대부분의 경우 페이로드는 여러 필드를 포함할 수 있는 객체여야하며 기록 된 mutations는 더 이해하기 쉽습니다.
// ...
mutations: {
increment (state, payload) {
state.count += payload.amount
}
}
store.commit('increment', {
amount: 10
})
mutations 을 이용하는 또 다른 방법은 type
속성을 가진 객체를 직접 사용하는 것입니다.
store.commit({
type: 'increment',
amount: 10
})
객체 스타일 커밋을 사용할 때 전체 객체는 mutations 핸들러에 페이로드로 전달되므로 핸들러는 동일하게 유지됩니다.
mutations: {
increment (state, payload) {
state.count += payload.amount
}
}
Vuex 저장소의 상태는 Vue에 의해 반응하므로, 상태를 변경하면 상태를 관찰하는 Vue 컴포넌트가 자동으로 업데이트됩니다.
Mutations는 무조건 동기적이어야 합니다.
Actions는 Mutations와 유사합니다.
다만 이 작업에는 임의의 비동기 작업이 포함될 수 있습니다.
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment (state) {
state.count++
}
},
actions: {
increment (context) {
context.commit('increment')
}
}
})
Actions 핸들러는 저장소 인스턴스의 같은 메소드들/프로퍼티 세트를 드러내는 컨텍스트 객체를 받습니다.
그래서 context.commit을 호출하여 변이를 커밋하거나 context.state와 context.getters를 통해 상태와 getters에 접근 할 수 있습니다.
실제로 (특히 commit
를 여러 번 호출해야하는 경우)코드를 단순화하기 위해 ES2015 전달인자 분해 (opens new window)를 사용합니다.
actions: {
increment ({ commit }) {
commit('increment')
}
}
💡 액션은
store.dispatch
메소드로 시작됩니다.
액션 내에서 비동기 작업을 수행 할 수 있습니다.
store.dispatch('increment')
💡 저장소를 모듈 로 나눌 수 있습니다. 각 모듈은 자체 상태, 변이, 액션, 게터 및 심지어 중첩된 모듈을 포함 할 수 있습니다.
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'의 상태
모듈의 변이와 getter 내부에서 첫 번째 전달인자는 모듈의 지역 상태 가됩니다.
const moduleA = {
state: () => ({
count: 0
}),
mutations: {
increment (state) {
// state는 지역 모듈 상태 입니다
state.count++
}
},
getters: {
doubleCount (state) {
return state.count * 2
}
}
}
유사하게 모듈 내부에서 context.state
는 지역 상태를 노출시킬 것이고 루트 상태는 context.rootState
로 노출 될 것입니다.
const moduleA = {
// ...
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
}
}
}
프로젝트의 규모가 클수록 Vuex는 필수가 되어지는데요.
store의 설계에 주의하며 중앙 집중식 저장소 라는 개념이란것을 잘 파악하면 나름대로 쉽게 접근 할 수 있는것 같습니다.