본문 바로가기
Vue.js

[Vue.js] 22. use Vuex

by 청양호박이 2021. 4. 29.

이번에는 Vuex에 대해서 알아보겠습니다. Vuex는 vue.js로 제작한 web application에 대한 상태를 관리할 수 있도록 기능을 제공하는 Library입니다. 해당 application내 모든 components에 대한 공용저장공간이라고 생각하면 될 것 같습니다.

예를들면, 이전에 JWT를 구현하기 위해서 사용한 Session Storage도 공용저장공간의 역할을 수행하지요. 

SPA로 제작하는 web application의 경우에는 다양한 components가 동일한 data를 참조해야 할 수 있습니다. 해당 data는 유기적으로 변하지만 어느 시점에서는 모두 동일하게 보여야 하는 것이죠. 이를 좀더 구체화된 단어로는 state(상태)라고 Vuex에서는 정의합니다. 그리고 이 state를 기존 코드에서 분리하여 관리하는 기능을 제공하는 것이 바로 Vuex가 되는 것 입니다.

Vuex가 적용되지 않는 web application의 경우 components간 data를 전달하기 위해서는 Parent / Child로 역할을 구분하고, 둘 사이의 데이터를 아래와 같이 전달하게 됩니다. 

 

  • Parent to Child 전송: Props를 통한 전송
  • Child to Parent 전송: Emit Event를 통한 전송

 

이 경우에는 dom에서 인식해서 변경된 내용이 dynamic하게 적용됩니다. 이렇게 되면 단계가 깊어질수록 거미줄처럼 어렵게 됩니다. 이럴 경우에는 Vuex를 통해서 별도의 store에 데이터를 관리하는 것이 훨씬 효율적일 것 입니다.

[https://vuex.vuejs.org/kr/ 그림참조]

Vuex는 위과 같이 크게 3가지 모듈로 구성이 되어있습니다. 이 모든 모듈은 저장소라고하는 store내에 구성이 됩니다. 그리고 화살표로 볼 수 있듯이 단방향으로 데이터 흐름이 됨을 확인할 수 있습니다. 각 모듈에 대해서 약간 자세히 알아보면...

 

  • state - Vue components내에 작성되는 data와 같다고 생각하면 됩니다. 해당 값이 바뀌면 dom에서 인식해서 바로 변경이 적용됩니다. 그리고 자체 변경은 불가능합니다.
  • mutations - Vuex store에서 state의 값을 변경하는 유일한 방법입니다. 생김새는 methods와 같으며 인자로는 state자체를 받습니다. commit으로 호출을 수행하고, 추가로 payload를 commit할때 추가하여 전달할 수 있습니다. 
  • actions - state를 직접 변경하는 대신 mutations를 commit하는 방식으로 동작합니다.  context를 인자로 받고, dispatch로 호출을 합니다. 

 

아마 아주작은 web application이 아니라면, Vuex는 꼭 필요한 선택사항이 될 것 입니다. 그렇다면 Vuex를 설치하고 구성해 보도록 하겠습니다. 

 


1. Vuex 설치


설치를 위해서는 우선 npm repository에서 Vuex를 검색해 줍니다. 그리고 현재 버전등을 확인해서 아래와 같이 package.json에 dependency를 추가해 줍니다. 

[package.json]

  "dependencies": {
    "vue": "^2.5.2",
    "vue-router": "^3.0.1",
    "axios": "^0.21.1",
    "vuex": "3.6.2"
  },

그리고 해당프로젝트 경로에서 npm install을 수행합니다. 그럼 아래와 같이 메시지가 뜨고 정상 설치가 완료됩니다.

추가적으로 Vuex는 비동기처리를 위한 Promise를 필요로 합니다. 만약에 IE까지 지원대상으로 포함을 한다면... polyfill library가 필요합니다. 따라서 es6-promise를 추가로 설치하도록 하겠습니다. 역시나 npm repository에서 찾아봅니다.

[package.json]

  "dependencies": {
    "vue": "^2.5.2",
    "vue-router": "^3.0.1",
    "axios": "^0.21.1",
    "vuex": "3.6.2",
    "es6-promise": "4.2.8"
  },

역시나 npm install을 수행합니다. 그럼 아래와 같이 메시지가 뜨고 정상 설치가 완료됩니다.

 


 

 


 

2. Vuex 사용 Basic


우선 기본적인 사용법을 확인하기 위해서 단일 Store환경으로 구성해 보겠습니다. Store를 별도로 관리하기 위해서 src폴더 밑에 store폴더를 추가해 줍니다.

store의 구조는 위와 같습니다. 우선 store.js를 추가하고 main.js에서는 해당 js를 기존에 axios를 추가하는 방식으로 구현해 보겠습니다. 

 

[src > store > store.js]

import Vue from 'vue';
import Vuex from 'vuex';
import 'es6-promise/auto';

Vue.use(Vuex);

export default new Vuex.Store({
  state: {
    userId: 'default',
  },
  mutations: {
    chgUserId(state, newId) {
      state.userId = newId;
    }
  },
  actions: {
    callMutation({commit, state}, newId) {
      commit('chgUserId', newId);
    }
  },
});

우선 위에서 설치한 Library들을 모두 import해 줍니다. 그리고 Vuex의 Store객체를 하나 생성하고 그 안에 구성요소인 state, mutations, actions를 구성합니다. 저는 테스트를 위해서 userId를 Store의 저장소에서 관리하는 하나의 변수로 지정하였습니다. 

 

그리고, mutations에서는 newId가 payload로 들어오면 저장소내 userId를 변경합니다. 마지막으로 actions에서는 비동기로 mutations의 method를 commit하고 이때, 기존과 동일하게 payload의 값을 기준으로 newId를 변경시키게 됩니다. 

 

이렇게 Store에 대한 구성이 끝났으면... 이를 전역으로 사용하기 위해서 main.js에 해당 js를 import해주고 반영해 줍니다.

 

[src > main.js]

import Vue from 'vue';
import vuetify from '@/plugins/vuetify';
import axios from  '@/plugins/axios';
import App from './App';
import router from './router';
import store from '@/store/store';

Vue.config.productionTip = false;

/* eslint-disable no-new */
new Vue({
  el: '#app',
  vuetify,
  router,
  store,
  components: { App },
  template: '<App/>',
});

이제 Store에 대한 준비는 끝났습니다. 그렇다면 vue파일을 하나 추가로 구성해서 실질적으로 정상 동작하는지 확인해 보겠습니다. 

 

[Vuex.vue]

<template>
  <div>
    <h1>{{ userId }}</h1>
    <p/><p/><p/>
    <button @click="changeIdWithMutation">changeIdWithMutation</button>
    <p/><p/>
    <button @click="changeIdWithAction">changeIdWithAction</button>
  </div>
</template>

<script>
export default {
  data() {
    return {

    };
  },
  computed: {
    userId() {
      return this.$store.state.userId;
    },
  },
  methods: {
    changeIdWithMutation() {
      console.log('직전ID : ', this.userId);
      console.log('changeIdWithMutation clicked');
      this.$store.commit('chgUserId', 'AyoteraLab');
    },
    changeIdWithAction() {
      console.log('직전ID : ', this.userId);
      console.log('changeIdWithAction clicked');
      this.$store.dispatch('callMutation', 'AyoteraLab Actions');
    },
  }
}
</script>

<style>

</style>

기존에 보통 보던 vue코드와 다른점이 있습니다. vue내에서 사용하는 변수는 모두 data( ){...} 내에 기술하고 내부에서 this.valueName으로 사용했었습니다. 하지만 Store내 state에 정의된 변수를 사용하기 위해서는 이 방법을 적용하면 dom에 실시간 변경내용이 반영되지 않습니다. 

 

예를들어서....

  data() {
    return {
      userId: '',
    };
  },
  mounted() {
    this.userId = this.$store.state.userId;
  },

이런식으로 data( )내의 변수에 Store내 state의 변수를 매핑하면 안된다는 것 입니다. 이를 정상적으로 구현하기 위해서는 위의 코드대로 computed로 관리를 해주어야 합니다. 그리고 mutation과 action은 각 각 commit과 dispatch로 호출해서 사용하였습니다. 

 

[결과]

 

1. 초기화면 - 정상적으로 default 초기값이 computed 통해서 반영되어 표시됨

2. changeIdWithMutation 버튼클릭 - 정상적으로 payload값으로 변경하고 dom에 실시간 반영되어 표시됨

 

 

3. changeIdWithAction 버튼클릭 - 정상적으로 payload값으로 변경하고 dom에 실시간 반영되어 표시됨

 

이렇게 간단하게 사용법을 알아보았습니다. 해당 Store는 해당 web application의 모든파일에서 동일하게 적용하여 구현할 수 있습니다. 

 



 

3. Vuex Store의 모듈화


마지막으로 Store를 모듈화하여 구성하는 방법에 대해서 알아보겠습니다. Store의 경우 모든 state가 한번에 전달이 되기 때문에 실제로는 기능별로 Store에 대한 분리가 필요합니다. 이를 위해서 Vuex에서 제공하는 방식은 Store의 모듈화 입니다. 

 

Main Store내에 항목별로 여러개의 Store를 sub로 구성하고, 각 각의 Store내에 state, mutations, actions를 구성하는 방식입니다. 

 

예로써 기존에 작성했던 store.js를 모듈화하여 구성해 보겠습니다. 절차는 아래와 같습니다. 

 

  1. store폴더내 index.js를 작성하여 modules관련 구성으로 코드구성
  2. index.js에 import할 sub store를 js로 구성
  3. main.js에 신규로 구성한 main store를 import 및 전역 추가
  4. 개별 vue 파일에 sub store에 구성한 state, mutations, actions를 사용하기 위한 수정

 

[1. store폴더 구성변경 및 main store 구성]

전체 폴더는 위와같이 구성합니다. 기존에 store는 유지하고 이 안에 index.js가 main store가 됩니다. main.js에서는 import를 폴더로 하게 되면 자동적으로 index.js가 import대상 파일이 되기 때문에 해당방식으로 구현합니다. 그리고 모듈화를 통한 sub store는 modules폴더를 추가하여 구성합니다.

 

그럼 index.js를 살펴보겠습니다. 

 

[src > store > index.js]

import Vue from 'vue';
import Vuex from 'vuex';
import 'es6-promise/auto';
import session from './modules/session';

Vue.use(Vuex);

export default new Vuex.Store({
  modules: {
    session,
  },
});

 

기존에 store.js와 유사한데 뭔가 많이 비어보입니다. 네... state, mutations, actions들이 사라지고 modules만 추가되어 있습니다. 이것이 바로 모듈화의 시작입니다. index.js는 sub store를 담기위한 큰 그릇일 뿐이고, sub modules는 이 안에 정의만 하도록 합니다.

 

이를 위해서, sub modules를 import해주고 modules안에 import한 store를 정의합니다.  

 

 

[2. sub store 구성]

 

그렇다면 위에 import한 sub store를 정의하겠습니다.  

 

[src > store > modules > session.js]

// initial state
const state = {
  userId: 'default',
};

// getter
const getter = {};

// mutations
const mutations = {
  chgUserId(state, newId) {
    state.userId = newId;
  },
};

// Actions
const actions = {
  callMutation({commit, state}, newId) {
    commit('chgUserId', newId);
  },
};

export default {
  namespaced: true,
  state,
  getter,
  mutations,
  actions,
};

각 sub store를 정의하는 js 파일에는 구성 요소만 정의하고, 이를 export로 묶어줍니다. 따라서 state, mutations, actions를 정의만 하고 마지막에 export default를 수행해 줍니다. 

 

아참!! 이때, namespaced: true를 꼭 넣어줍니다. 왜냐하면, 개별 vue파일에서는 sub store의 mutations, actions를 구별할 방법이 없기 때문에... 네임스페이스로 등록하여 경로구조로 접근이 가능하게 됩니다. 

 

 

[3. main.js에 변경 구조 적용]

 

현재 main.js에는 기존의 store.js가 적용되어 있습니다. 모듈화된 Store를 적용하기 위해서는 import부분만 변경해 주면 됩니다. 

 

[src > main.js]

//import store from '@/store/store';
import store from './store';

이렇게 해주면, store폴더내 index.js가 등록되어... main store가 활성화 됩니다.

 

 

[4. 개별 vue파일의 호출 수정]

 

sub store의 state, mutations, actions을 호출하는 방법은 약간 차이가 있습니다. 

 

  • state - this.$store.state.[sub store명].[state내 변수명]으로 호출
  • mutations - this.$store.commit( '[sub store명] / [mutations내 method명]' )으로 호출
  • actions - this.$store.dispatch( '[sub store명] / [actions내 method명]' )으로 호출

 

말로는 와닿지가 않으니... 코드로 확인해보겠습니다. 

 

[Vuex.vue]

<template>
  <div>
    <h1>{{ userId }}</h1>
    <p/><p/><p/>
    <button @click="changeIdWithMutation">changeIdWithMutation</button>
    <p/><p/>
    <button @click="changeIdWithAction">changeIdWithAction</button>
  </div>
</template>

<script>
export default {
  data() {
    return {

    };
  },
  computed: {
    userId() {
      return this.$store.state.session.userId;
    },
  },
  methods: {
    changeIdWithMutation() {
      console.log('직전ID : ', this.userId);
      console.log('changeIdWithMutation clicked');
      this.$store.commit('session/chgUserId', 'AyoteraLab');
    },
    changeIdWithAction() {
      console.log('직전ID : ', this.userId);
      console.log('changeIdWithAction clicked');
      this.$store.dispatch('session/callMutation', 'AyoteraLab Actions');
    },
  }
}
</script>

<style>

</style>

이렇게 기본적인 사용방법 및 모듈화에 대해서 알아보았습니다. 아마 프로젝트에 적용되는 부분은 나중에 다른 파트에서 다루지 않을까 생각됩니다. 

 

- Ayotera Lab -

댓글