Vuex란
Vuex문서에 정의 되어있는 것을 보면 Vuex는 Vue.js에서 사용하는 중앙 집중형 상태 관리 패턴 라이브러리라고 합니다.
좀더 쉽게 설명하자면 그림과 같이 뎁스가 깊은 컴포넌트에서 다른 방향의 뎁스가 깊은 컴포넌트에게 이벤트를 감지할 수 있게 하는 경우에는 어떤식으로 코드를 작성해야할까요? 컴포넌트 맨 상위로 emit
을 하고 목적지까지 props
를 할 경우에는 동선이 매우 비효율적일 것입니다. 이때 상태관리를 도입하면 단 한번의 경로만 거쳐서 목적 컴포넌트까지 도달하게 됩니다. 또한 공통 로직같은 경우에 Vuex에 선언하여 여러 컴포넌트에서 공통 함수를 사용할 수 있게 할 수 있습니다.
이러한 방식은 eventBus나 Global Variable을 선언하는 방식으로 사용할 수 있을것입니다. 그러나 eventBus의 경우에는 공통 데이터들을 관리해줄 수가 없고, Global Variable을 선언해서 사용하면 해당 데이터를 변경한 컴포넌트나 메소드들을 추적할 수 없게 될 것입니다.
그래서 상태 관리 뿐만아니라 디버깅에도 용이한 Vuex를 사용하게 되는 것 입니다.
Vuex 구성요소
vuex 구성요소는 여러가지가 있지만 그중 대표적인 것을 3개만 뽑자면 다음과 같습니다
state
: 상태(데이터)는 컴포넌트들이 공통으로 사용할 수 있는 변수와 같은 개념입니다. 컴포넌트별로 data를 관리한 것을 vuex store로 옮겨서 사용한다고 생각하면 됩니다.mutation
: 상태에 대한 변이 함수를 정의하는 곳입니다. vuex에서는state
를 사용하는 컴포넌트에서 직접 변경하지 않고mutation
을 통해 변경해야 합니다.action
: api 호출을 하는 것과 같은 비동기, 비순차적으로 실행되어야 하는 함수를 정의해 놓은 곳 입니다.
그 외 rootState
, rootMutation
, rootAction
, getters
등이 있습니다.
Vuex 문서에 의하면 다음 그림과 같이 컴포넌트에서 액션을 호출하면 변이가 발생하여 변이된 상태가 다시 컴포넌트에 렌더링이 됩니다.
컴포넌트에서 사용 방법
초기 세팅
보통 상태관리 저장소라해서 store
라는 이름을 사용합니다. state
, mutation
, action
을 정의해 놓을 store.js
를 만듭니다.
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
const store = new Vuex.Store({
state: {},
mutations: {},
actions: {}
});
export default store;
그리고 이 store
를 사용하려는 컴포넌트 쪽 Vue에 추가하면 됩니다.
import Vue from 'vue'
import index from './index.vue'
import store from "./store";
new Vue({
el: '#app',
store: store,
render: h => h(index)
});
state 사용
/* store.js */
...
state: {
contents: ["1", "2", "3"]
},
...
<template>
...
</template>
<script>
...
methods: {
getContents() {
return this.$store.state.contents;
}
}
</script>
this.$store.state.{상태명}
과 같은 방식으로 사용하면 됩니다. 만일 매번 이렇게 길게 쓰는 방식이 마음에 안든다면 computed에 정의해서 사용할 수 있습니다.
<template>
<p>
{testContents}
</p>
</template>
<script>
...
computed: {
testContents() {
return this.$store.state.testContents;
}
},
methods: {
getContents() {
...
const contents = testContents;
...
}
}
</script>
mutation 사용
<!--vue component-->
<template>
...
</template>
<script>
...
methods: {
commitPage(page) {
this.$store.commit("changePage", page)
}
}
</script>
/* store.js */
...
state: {
page: 0
},
mutations: {
changePage(state, payload) {
state.page = payload;
}
}
...
mutation
을 사용하려면 commit
메소드를 호출하면 됩니다. commit
메소드의 인자로는 mutations
에 정의된 메소드명이 필요합니다. 해당 메소드에 추가적인 파라미터가 필요한다면 commit
의 두번째 인자에 넘겨줄 변수를 넣어주면 됩니다. 만일 두개 이상의 인자가 필요할 경우 Object형태로 넘겨주어 해당 메소드에서 꺼내서 쓰는 방식으로 사용할 수 있습니다.
mutations
에 정의된 메소드는 첫번째 파라미터로 무조건 현재 상태의 state
를 받습니다. 그리고 추가적으로 넘어오는 인자가 있다면 두번째 파라미터에서 받게 됩니다.
Action 사용
/* store.js */
...
actions: {
addAsync(context, payload) {
setTimeout(() => {
context.commit('add', payload);
}, 1000)
}
}
...
<!--vue component-->
<template>
...
</template>
<script>
...
methods: {
incrementAsync(number) {
this.$store.dispatch("addAsync", number);
}
}
</script>
action
의 경우 dispatch
메소드로 호출하게 됩니다. mutations
의 commit
과 마찬가지로 첫번째 인자로는 actions
메소드명이 되고, 두번째 인자로는 해당 메소드에 인자로 넘겨줄 값을 넣어줍니다. 두개 이상의 값을 전달해야하는 경우 개체로 전달해주어야 합니다. action
메소드의 첫번째 인자값으로 context
로 되어있는데, context
를 로그로 찍어보면 state
, commit
, dispatch
, rootCommit
등 여러가지가 있는 것을 볼 수 있습니다. 위의 예시에서는 context
에서 commit
을 꺼내 사용했지만 만일 dispatch
, commit
만 꺼내서 사용하고 싶다하면 다음과 같이 사용할 수 있습니다.
addAsync({commit, dispatch}, payload) {
setTimeout(() => {
commit('add', payload);
dispatch('otehrMethod', payload);
}, 1000)
}
action
에 선언된 메소드는 Promise 반환형이기 때문에, 비동기 반환 이후의 로직을 정의 수 있어 비동기 처리에 효율적으로 대응할 수 있습니다.
dispatch("addAsync").then(() => {
console.log("async return");
})