Back
Featured image of post [Vue] Vuex 개념과 실제 사용해보기!

[Vue] Vuex 개념과 실제 사용해보기!

Vuex란 무엇이고 어떻게 사용할까?

Vuex

vuex는 vue.js에서 컴포넌트들의 상태 관리를 위한 효율적인 라이브러리이다.
React의 Flux 이름과 비슷한것을 보면 Flux 패턴에서 차용한 것이라고 한다.

Vuex를 사용하면 좋은 점

기본적으로 vue 생태계에서 컴포넌트 간 여러 정보들을 관리하려면 부모-자식 관계로 데이터들을 넘겨주고 받아야한다. 하지만 현실적으로 복잡한 컴포넌트 관계속에서 계속 주고 받으려면 여러 컴포넌트 파일에서 data 속성을 관리해주어야 하는 단점이 있다. vuex02

예를 들어 위 사진처럼 깊은 관계에서 다른 컴포넌트로 데이터를 보내려면 부모 컴포넌트를 계속 찾아 이벤트를 바인딩 시키고 다시 props로 내려주어야 하는 불편함이 있다. 하지만 vuex를 사용하게 된다면 아래와 같은 구조로 더욱 단순하게 작업할 수 있다. store이라는 일종의 중앙 저장소에서 관리할 수 있게 된다.

after-vuex

Vuex 패턴

Vuex에서는 어떤 패턴으로 상태들을 관리하는지 간략하게 설명해보자면, State, View, Actions라는 단방향 패턴 흐름으로 데이터들을 관리한다. vuex

각각의 역할은 다음과 같다.

  • state : 컴포넌트 간 공유하는 데이터 속성 (컴포넌트에서의 data 역할)
  • View : 데이터를 표시하는 화면
  • Actions : 사용자 입력에 따라 데이터를 변경하는 메서드

주요 속성

vuex를 사용하려면 store 인스턴스를 구성해주어야 하는데 state, getters, mutations, actions 총 4개의 속성으로 구성되어 있다. 각각의 역할이 다르며 여러 컴포넌트에서 사용할 상태 값을 정의히고(state) 서버로부터 api 통신을 통해 비동기적으로 받아오거나(actions), 필요에 따라 상태 값들을 변경하고(mutations), 필요한 컴포넌트에게 실시간으로 전달해주는(getters) 역할을 한다.

//store.js
import Vuex from 'vuex';
export const store = new Vuex.Store({
    state: {},
    getters: {},
    mutations: {}
    actions: {}
});

state

컴포넌트 간 공유하는 데이터들을 관리하는 역할이다.

state: {
  message: 'hello vuex!';
}

정의된 상태 값이 필요한 컴포넌트에서는 아래와 같이 받아올 수 있다.

<!--component template -->
<template>
  <h2>{{this.$store.state.message}}</h2>
</template>

getters

연산된 state에 접근해 데이터를 조작하는 역할을 한다. (일종의 computed 역할)

state : {
    hitCount : '',
},

getters : {
    upHitCount : function(state) {
        return state.hitCount++;
    }
}

mutations

state값을 변경하는 역할을 한다. (일종의 methods 역할)

state : {
    todoList : []
},

mutations : {
    setBoardList: function(state, item) {
        return state.todoList.push(item);
    },
}

commit메서드로 호출할 수 있다. (첫 인자에 해당 메서드 이름을 넘기고, 두번째 인자에 데이터를 넘겨준다.)

this.$states.commit('setBoardList', {
  id: 1,
  task: '공부하기'
});

actions

비동기 처리를 담당한다. 여러 컴포넌트에서 mutations로 비동기 이벤트가 일어나면 수행 시간 차이로 state가 변경될 경우 추적이 어렵기 때문에 actions에 비동기 로직들을 선언하면된다. 예를들어 api 요청, ES6 Async함수(Promise), setTimeout() 등의 로직들을 여기에 선언하면 된다.

commit메서드로 mutation 속성내의 메서드에 접근할 수 있으며, 컴포넌트에서 호출할 때는 dispatch메서드로 접근한다.

state : {
    menuList : []
},

mutations : {
    //state 데이터에 삽입
    setMenuList: function(state, list) {
        return state.menuList = list;
    }
},

actions : {
    //api 요청
    requestMenuList: function(state) {
        axios.get('/category/list').then(function (response) {
            state.commit('setMenuList', response.data.items);
        });
    }
}
//vue-component
created() {
    this.$store.dispatch('requestMenuList');
}

실습

사실 Vuex가 힘을 발휘하는 순간은 복잡한 대규모 웹 애플리케이션이지만.. 문법을 사용해보는 취지로 글쓰기-게시판 형태의 단순한 페이지로 문법을 익혀보았다. 간단히 내용을 입력하는 컴포넌트와 이를 출력해주는 컴포넌트로 구성되어 있는 페이지이다. Dec-03-2021 17-40-53

Vuex 설치하기

먼저 vuex 라이브러리를 설치한다.

npm install vuex

store 인스턴스 생성

store 인스턴스를 정의해주는 단계이다. 보통 프로젝트 폴더에서 src\store디렉토리를 만들어서 여러 store들을 관리한다.
(이번 포스팅에서는 store 1개로 관리하지만, 복잡한 웹 페이지에서는 여러 store로 모듈화할 수 있다.)

위 구현 사진에서 write 버튼을 누르면 글을 입력하는 컴포넌트 (팝업)이 켜졌다가 글을 입력하면 꺼지는 동작이 필요하기 때문에 popupFlag라는 상태 값을 정의하였고, 게시글을 관리 할 boardList 배열도 정의하였다.

// src\store\store.js
import Vuex from 'vuex';
//vuex
export const store = new Vuex.Store({
    state: {
        boardList: [], //게시글 배열
        popupFlag: false //true : 팝업창 열림, false : 꺼짐
    },
    getters: {},
    mutations: {}
    actions: {}
});

mutations에는 팝업 상태를 관리하는 이벤트와 게시글을 넣어주는 이벤트가 필요하기 때문에 필요한 메서드들을 정의하였다.
(외부 서버 통신이 필요하지 않기 때문에 actions를 생략하였고, 단순히 값만 주고받는 흐름이기 때문에 getters도 생략..ㅎㅎ)

import Vuex from 'vuex';
//vuex
export const store = new Vuex.Store({
  state: {
    boardList: [], //게시글 배열
    popupFlag: false //true : 팝업창 열림, false : 꺼짐
  },

  mutations: {
    setBoardList: function (state, item) {
      state.popupFlag = false;
      return state.boardList.push(item);
    },

    onPopup: function (state) {
      return (state.popupFlag = true);
    },

    offPopup: function (state) {
      return (state.popupFlag = false);
    }
  }
});

이제 설치한 vuex와 정의한 store 인스턴스를 vue에 등록해야하는데 main.js에 등록해주면 된다.

import { createApp } from 'vue';
import Vuex from 'vuex';
import { store } from './store/store';
import App from './App.vue';

const vuexApp = createApp(App);
vuexApp.use(Vuex);
vuexApp.use(store);
vuexApp.mount('#app');

state 값 받아오기

예습 프로젝트에서 컴포넌트 구조는 게시글을 보여주는 메인 컴포넌트와 게시글을 입력하는 팝업 컴포넌트로 구성되어 있다. 팝업 컴포넌트는 popupFlag가 on 일 때 보여주어야 하므로 아래와 같이 클래스 바인딩을 해주면 된다. 핵심은 store에 접근할 때 $store키워드로 접근한다는 점이다.

<template>
  <!-- popupFlag가 true일 때 hide 클래스 바인딩 -->
  <section class="write-wrap" v-bind:class="{hide : !this.$store.state.popupFlag}">
    <!-- popup 마크업 -->
  </section>
</template>

mutations 메서드 호출하기

만약 글쓰기를 취소할 때에도 popupFlag를 변경해야하는데 togglePopup 메서드에서 헬퍼로 offPopup을 호출한다. 갑자기 스프레드 연산자가 나와서 당황스럽게지만, vuex에서는 헬퍼 함수라는 기능이 있어 컴포넌트에서 더욱 쉽게 vuex 속성에 접근하는 기능을 제공한다.

<!-- template -->
<div class="article-button">
  <button type="button" class="cancel" v-on:click="togglePopup">cancel</button>
</div>

컴포넌트에서 store에 정의된 mutations에 접근하려면 this.$store.commit('offPopup')을 단순히 호출만 하는 메서드를 하나 생성해야하는데 헬퍼를 사용하면 삼항연산자로 mutations에 이미 존재하는 메서드를 그대로 가져올 수 있어서 편하다. 호출 key만 정해주면 된다.

// popup component script
import { mapMutations } from 'vuex';
export default {
  methods: {
    ...mapMutations({
      sendMessage: 'setBoardList',
      togglePopup: 'offPopup'
    })
  }
};

실제로 글을 등록하는 버튼에도 mutations에 정의된 setBoardList()메서드를 연결 해주어야 한다.

<button type="button" class="write" v-on:click="sendMessage(this.constructMessage())">send</button>
import { mapMutations } from 'vuex';
export default {
  data() {
    return {
      id: '',
      message: ''
    };
  },
  methods: {
    ...mapMutations({
      sendMessage: 'setBoardList',
      togglePopup: 'offPopup'
    }),

    constructMessage: function () {
      return {
        date: new Date().toISOString().substr(0, 10).replace(/-/g, ''),
        id: this.id,
        message: this.message
      };
    }
  }
};

메인 컴포넌트에서 boardList를 출력해주고 있었다면 잘 받아와지는 모습을 볼 수 있다.

<template>
  <main v-bind:class="{popup : this.$store.state.popupFlag}">
    <h2 class="hide">Recent POST</h2>
    <ol class="list">
      <li class="post-list" v-for="(item) in this.$store.state.boardList" :key="item.id">
        <details>
          <summary>{{item.message}}</summary>
          <p>{{item.message}}</p>
        </details>
        <div class="post-info">
          <p><span class="author">{{item.id}}</span> | <span class="date">{{formatDate(item.date)}}</span></p>
        </div>
      </li>
    </ol>
  </main>
</template>

Dec-03-2021 22-28-30

이번 예제의 전체 소스는 이곳에서 확인하실 수 있습니다!

참고자료