개발 일기

[Vue.js 시작하기] - vuex로 중앙에서 상태 관리 하기 본문

Vue.js

[Vue.js 시작하기] - vuex로 중앙에서 상태 관리 하기

윤지 2021. 1. 19. 22:17

Vuex란?

Vue.js 애플리케이션에 사용할 수 있는 상태 관리 라이브러리다. 이를 사용하면 모든 컴포넌트에 대한 중앙 집중식 저장소 역할을 할 수 있다. 상단 인스턴스는 여러개의 많은 하위 컴포넌트로 설계되어 구성된다. 

 

여태까지 컴포넌트들끼리의 데이터 전달은 이벤트버스 등을 사용하였지만 공동의 상태를 공유하는 여러 컴포넌트가 있다는 등 프로젝트의 규모가 커진다면 이벤트 추적 관리에 힘들어질 수 있다. 이는 싱글톤으로 관리되지 않아 컴포넌트가 공유된 상태를 추출하지 않을 수 있다는 말과 같다.

 

이를 효율적으로 관리하기 위해서는 vuex 라이브러리를 사용하면 된다. 

 

설치

npm install vuex --save

 

store란?

vuex로 상태관리를 하기 위해서는 store에 대해 먼저 이해해야 한다. 쉽게 말해서 store는 애플리케이션의 공유된 상태를 보유하고 있는 전역변수라고 할 수 있다.

 

다른 라이브러리 설치 후 종속성 추가할 때처럼 main.js에 바로 추가해도 되지만, src/store 아래에 js 파일을 만들어서 따로 관리할 예정이다. 이는 클래스도 성격별로 세분화되는 것처럼 이후에 전역변수도 여러개가 되는 경우 모듈화를 간편하게 하기 위해서다. 모듈화는 다음 글에서 다룰 예정이다.

 

index.js

//src/store/index.js

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)


const store = new Vuex.Store({
    state: {

    },
    mutations: {
        
    },
    getters:{

    },
    actions:{

    }
})

export default store

store의 핵심 구성 요소들은 state, mutations, actions, getters이다.

 

main.js

import Vue from 'vue'
import App from './App'
import router from './router'
import Vuetify from 'vuetify'
import 'vuetify/dist/vuetify.min.css'
import 'material-design-icons-iconfont/dist/material-design-icons.css'
import axios from 'axios'
import store from '@/store/index.js' //store 전역변수를 import 해온다

Vue.prototype.$eventBus = new Vue();
Vue.prototype.$axios = axios

export default new Vuetify({
  icons: {
    iconfont: 'md' // 'md' || 'mdi' || 'fa' || 'fa4' 
  }
})

Vue.use(Vuetify)

new Vue({
  el: '#app',
  router,
  components: { App },
  template: '<App/>',
  vuetify: new Vuetify(),
  store //Vue에 store 사용 명시
})

store를 외부 js 파일에 등록하였다면 이를 import 하여 Vue에서 사용한다는 것을 명시해야한다.

 

state

state는 여러 컴포넌트에서 공유할 공동의 상태를 의미한다.

//src/store/index.js

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

const store = new Vuex.Store({
    state: {
        count: 1
    }
})

export default store
<template>
  <div>
    {{ this.$store.state.count }}
  </div>
</template>

store에 count라는 상태를 1이라고 정의해두었고, 어느 컴포넌트에서든 this.$store.state를 사용하여 공유할 상태에 있는 count 변수에 접근할 수 있다.

 

mutations

mutations는 state를 변경시키기 위한 유일한 방법이다. 또한 동기적 로직을 정의할 수 있다.

//src/store/index.js

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

const store = new Vuex.Store({
    state: {
        count: 1
    },
    mutations: {
        doubleCount(state) {
            state.count = state.count * 2
        },
        multiCountAndNumber(state, payload) {
            state.count = state.count * payload
        }
    }
})

export default store

mutations에 state를 변경할 수 있는 함수를 정의한 모습을 볼 수 있다. 첫번째 parameter는 원하는 state를 변경하기 위하여 state를 받는다. 만약 넘겨받은 것으로 state를 변경할 것이 있다면 두번째에 parameter에 추가 전달인자를 받는다. 위의 예제에서는 관용적으로 payload라고 사용했다.

 

<template>
  <div>
    <v-card max-width="500" class="mx-auto" :height="500" style="padding: 20px">
      <v-row>
        <v-col cols="6"> {{ this.$store.state.count }}</v-col>
        <v-col cols="6">
          <v-btn @click="doubleCount">store count double</v-btn></v-col
        >
      </v-row>

      <v-row>
        <v-col cols="6">
          <v-text-field label="input number" v-model="number"></v-text-field>
        </v-col>
        <v-col cols="6">
          <v-btn @click="multiInputNumber">multi store and input</v-btn>
        </v-col>
      </v-row>
    </v-card>
  </div>
</template>

<script>
export default {
  data: function () {
    return {
      number: 0,
    };
  },
  methods: {
    doubleCount() {
      this.$store.commit("doubleCount");
    },
    multiInputNumber() {
      this.$store.commit("multiCountAndNumber", this.number);
    },
  },
};
</script>

this.$store.commit을 사용하여 이전에 mutations에 정의해둔 함수를 호출할 수 있다. doubleCount()를 보면 store의 doubleCount 핸들러를 실행시키므로 공유 상태 state중 count가 두배로 set 될 것이다. 다음으로 multiInputNumber()를 보면 store의 multiCountAndNumber를 실행시키므로 공유 상태 state중 count와 입력받은 값이 곱해진 결과값으로 count가 set 될 것이다.

 

doubleCount 네번 클릭해서 네제곱 되는 모습

 

 

입력된 값이 현재 state와 곱해지는 모습

 

getters

공동의 상태를 계산하여 state의 값을 반환하는 함수다. vue.js의 computed 속성과 비슷하다고 할 수 있다.

(computed 관련 참고 : 2021/01/14 - [Vue.js] - [Vue.js 시작하기] - computed vs watch)

 

[Vue.js 시작하기] - computed vs watch

Vue에는 변화를 감지하는 프로퍼티인 computed와 watch가 있다. 비슷한 성격탓에 공식 문서에도 함께 다루는 것을 볼 수 있다. 충분히 헷갈릴 수 있지만 목적을 보면서 사용한다면 상황에 맞게 잘 쓸

developerjournal.tistory.com

 

//src/store/index.js

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

const store = new Vuex.Store({
    state: {
        alphabet: " ABC "
    },
    getters: {
        getLowerCaseAlphabet(state) {
            return state.alphabet.toLowerCase();
        },
        getTrimAlphabet(state) {
            return state.alphabet.trim();
        }
    },
    actions: {

    }
})

export default store
<script>

  computed: {
    getLower() {
      return this.$store.getters.getLowerCaseAlphabet;
    },
    getTrim() {
      return this.$store.getters.getTrimAlphabet;
    },
  },
  
</script>

공동의 상태를 가공하는 경우가 여러 컴포넌트에 있을 때, 각 컴포넌트의 computed에서 같은 코드를 여러번 쓸 것을 getters에서 한번 제공하여 다른 컴포넌트에서 똑같이 쓸 수 있기 때문에 유지보수에 좋고 효율적인 코드를 사용할 수 있다. 

 

actions

actions는 mutations와 유사하다. 다른 점은 mutations가 동기적 로직을 정의할 수 있는 반면 actions는 비동기적 로직을 정의할 수 있다. 따라서 각 컴포넌트에서 호출할 때 async/await 키워드를 사용할 수 있다. 그리고 actions 내부에서 mutations에 대한 commit이 가능하다.

//src/store/index.js

import Vue from 'vue'
import Vuex from 'vuex'
import axios from "axios";

Vue.use(Vuex)

const HOST = ""

const store = new Vuex.Store({
    state: {
        animalList: [],
        fruitList: []
    },
    mutations: {
        setAnimalList(state, payload) {
            state.animalList = payload
        },
        setFruitList(state, payload) {
            state.fruitList = payload
        }
    },
    actions: {
        async getAllAnimals({ commit }) {
            let response = null
            try {
                response = await axios(HOST + "/api/getAllAnimal")
            }
            catch (e) {
                console.error(e)
            }
            commit("setAnimalList", response.data)
        },
        async getAllFriuts({ commit }) {
            let response = null
            try {
                response = await axios(HOST + "/api/getAllFruits")
            }
            catch (e) {
                console.error(e)
            }
            commit("setFruitList", response.data)
        }
    }
})

export default store

actions에 있는 함수의 파라미터에 commit 프로퍼티를 활성화 시켜서 내부에서 mutations에 대한 commit을 해주는 것을 볼 수 있다. 또한 비동기적 로직을 정의할 수 있기 때문에 만약 서버와의 통신이 필요하다면 axios를 사용하여 actions에 로직을 입력하면 된다. 

(axios 참고 : 2021/01/18 - [Vue.js] - [Vue.js 시작하기] - axios 사용하여 서버 통신 하기 )

 

[Vue.js 시작하기] - axios 사용하여 서버 통신 하기

Axios란? javascript 어플리케이션에서 서버 통신을 하기 위한 HTTP 통신 라이브러리다. 착각하면 안되는 것은 오직 Vue.js에 종속되는 것이 아니라 다른 js 어플리케이션에서도 일반적으로 많이 사용된

developerjournal.tistory.com

 

mutations와 마찬가지로 만약 넘겨받은 것으로 state를 변경할 것이 있다면 두번째에 parameter에 추가 전달인자를 받는다. 위의 예제에서는 관용적으로 payload라고 사용했다.

 

<script>
  async created() {
    await this.$store.dispatch("getAllAnimals");
    await this.$store.dispatch("getAllFriuts");
  }
 </script>

컴포넌트에서는 this.$store.dispatch를 사용하여 호출할 수 있다. 위의 예제에서는 순서대로 animal과 fruit를 get 해야하므로 async/await 키워드를 사용했다.

 

사실 actions에서 활성화시킬 수 있는 프로퍼티는 많지만 다음에 store 모듈화에 대해 언급할 때 더 얘기하도록 하겠다.

 

결론

vuex를 쓴다면 규모가 커진 프로젝트의 이벤트 추적 및 데이터 관리를 더 쉽게 할 수 있을 것이다. 

결론적으로 모든 컴포넌트는 컴포넌트 트리에 상관없이 공동의 상태에 액세스하거나 공동의 상태에 관련된 동작을 트리거 할 수 있다.

 

 

Comments