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 문서에 의하면 다음 그림과 같이 컴포넌트에서 액션을 호출하면 변이가 발생하여 변이된 상태가 다시 컴포넌트에 렌더링이 됩니다.

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메소드로 호출하게 됩니다. mutationscommit과 마찬가지로 첫번째 인자로는 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");
})