프론트 앤드(Front-End)/Vue

[Vue] 공용 저장소 Vuex 설치 및 사용법

RyanSin 2021. 2. 4. 01:01
반응형

- 지난 시간

안녕하세요. 지난 시간에는 Computed와 Watch 그리고 Method에 대해 알아봤습니다.

 

혹시 놓치고 오신 분들은 아래 링크를 통해 학습하고 오시는 걸 추천드리겠습니다.

any-ting.tistory.com/43

 

[Vue] Computed vs Watch vs Method 사용법 및 비교

- 지난 시간 안녕하세요. 지난 시간에는 Vue.js 생명주기, LifeCycle에 대해 알아봤습니다. 혹시 놓치고 오신 분들은 아래 링크를 통해 학습하고 오시는 걸 추천드리겠습니다. any-ting.tistory.com/42 [Vue] C

any-ting.tistory.com

- 개요

이번 시간에는 vue에서 많이 사용하는 상태 관리 라이브러리 vuex에 대해 알아보겠습니다.

 

상태 관리? 무슨 말인지 난해하죠... 여기서 말하는 상태는 State를 말합니다.

 

즉, Vue마다 사용하던 data() 속성(State)을, 새로운 공용 저장소 안에 data() 속성(State)을 정의하고 관리한다는 말입니다.

 

이렇게 되면  여러 Vue 인스턴스들은 해당 저장소에 접근해서 데이터를 서로 공유할 수 있습니다.

 

위 그림과 같은 구조로 데이터를 공유할 수 있습니다.

 

- 설치

Vuex는 설치 방법은 아래 링크를 통해 설치가 가능합니다.

 

vuex.vuejs.org/kr/installation.html

 

설치 | Vuex

설치 직접 다운로드 / CDN https://unpkg.com/vuex (opens new window) Unpkg.com (opens new window)은 NPM 기반 CDN 링크를 제공합니다. 위의 링크는 항상 NPM의 최신 릴리스를 가리킵니다. https://unpkg.com/vuex@2.0.0과 같은

vuex.vuejs.org

설치를 완료했다면 세팅을 해야죠? 혹시 vue-cli로 프로젝트를 생성할 때 vuex를 선택해서 생성했다면, 기본적으로 설정이 돼있습니다. 

 

store 폴더 vuex index.js 파일

 

main.js 파일 vuex 설정

render를 설정하는 main.js 파일 안에 있는 Vue 인스턴스에 vuex(store)를 import 하고 new Vue({store})안에 설정하면 됩니다.

 

이렇게 설정하면 this.$store로 접근이 가능합니다.

- 기본 구조

Vuex안에는 State, Getters,  Mutations, Actions, Modules가 있습니다.

 

- State (데이터 선언)

state 상태 관리해야 할 속성(데이터)을 선언하는 항목입니다.

 

선언 방식

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

Vue.use(Vuex);

export default new Vuex.Store({
  state: { //data
    Users: [
      {id: 1, name: "라이언", address: "Seoul"},
      {id: 2, name: "어피치", address: "Seoul"},
      {id: 3, name: "네오", address: "Seoul"},
      {id: 4, name: "무지", address: "Seoul"},
    ]
  },
  mutations: {
  },
  actions: {
  },
  modules: {
  },
});

 

Users 데이터를 정의했습니다. 그럼 호출을 해볼까요? 호출하는 방식은 아주 간단합니다.

 

템플릿 영역 안에서는 $store.state.Users로 호출할 수 있으며 스크립트 영역에서는 this.$store.state.Users로 호출할 수 있습니다.

 

호출 방식

<template lang='ko'>
    <div>
        Home Component
        <ul id="example-1">
          <li v-for="user in $store.state.Users">
            {{ user.name }}
          </li>
        </ul>
    </div>
</template>
<script>
export default {
  name: "HomeComponent",
  data() {
    return {
      user: { id: 4, name: '개발이 취미인 사람', address: 'Seoul' },
    };
  },
  created() {
    console.log("유저 목록 : ", this.$store.state.Users);
  },
};
</script>
<style lang='scss'>
</style>

   

실행 결과

위와 같은 방식으로 호출해도 괜찮지만 코드 규모가 커지게 되면 너무 보기 싫게 길죠?

 

이럴 때 사용할 수 있는 방법은 mapState 방식입니다.

 

mapState 방식으로 코드를 변경하겠습니다. 결과는 동일합니다.

 

호출 방식

<template lang='ko'>
    <div>
        Home Component
        <ul>
          <li v-for="user in Users">
            {{ user.name }}
          </li>
        </ul>
    </div>
</template>
<script>

// mapState import
import { mapState } from "vuex";

export default {
  name: "HomeComponent",
  data() {
    return {
      user: { id: 4, name: '개발이 취미인 사람', address: 'Seoul' },
    };
  },
  created() {
    console.log("유저 목록 : ", this.Users);
  },
  computed: {
    //mapState 데이터 등록
    ...mapState(["Users"]), // Users라는 변수명을 사용
    ...mapState({ Users: "Users" }), // 키 Users는 해당 컴포넌트에서 사용할 변수명 값은 State 값
  },
};
</script>
<style lang='scss'>
</style>

 

vuex를 import 하고 computed 속성에 등록해서 사용할 수 있습니다. 두 가지 방식으로 선언했으며, 선호하는 방식으로 사용하시면 됩니다.

 

- Getters (State 데이터 호출)

호출하는 방식은 동일합니다. 선언하는 방식은 약간 다른데요. 소스 코드를 통해 바로 확인하겠습니다.

 

선언 방식

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

Vue.use(Vuex);

export default new Vuex.Store({
  state: {
    Users: [
      { id: 1, name: '라이언', address: 'Seoul' },
      { id: 2, name: '어피치', address: 'Seoul' },
      { id: 3, name: '네오', address: 'Seoul' },
      { id: 4, name: '무지', address: 'Seoul' },
    ],
  },
  getters: {
    Users: state => {
      return state.Users;
    }
  },
  mutations: {
  },
  actions: {
  },
  modules: {
  },
});

 

getters 항목 안에 Users 함수를 만들었습니다. "첫 번째 파라미터는 state"를 가져옵니다.

 

선언할 때 꼭 화살표 함수 방식으로 선언해야 합니다. 그래야 state에 접근을 할 수 있습니다. (아주 중요!!)

 

getters는 두 번째 파라미터로는 getters를 가져올 수 있습니다.

EX)

  getters: {
    Users: (state, getters) => {
      return state.Users;
    }
  },

 

호출 방식

<template lang='ko'>
    <div>
        Home Component
        <ul>
          <!-- mapGetters 데이터 사용 -->
          <li v-for="user in Users">
            {{ user.name }}
          </li>
        </ul>
    </div>
</template>
<script>

// mapGetters import
import { mapGetters } from "vuex";

export default {
  name: "HomeComponent",
  data() {
    return {
      user: { id: 4, name: '개발이 취미인 사람', address: 'Seoul' },
    };
  },
  created() {
    console.log("유저 목록 : ", this.Users);
  },
  computed: {
  
    //mapGetters 함수 등록
    ...mapGetters(["Users"]),
    ...mapGetters({ Users: "Users" }),
  },
};
</script>
<style lang='scss'>
</style>

$store.getters.Users 방식으로 호출할 수 있습니다.

 

- Mutations (상태 접근)

mutations는 state를 직접 접근해서 수정했을 때 위험 상황을 방지해줍니다.

this.$store.state.Users 호출해서 값을 직접 수정하는 것은 데이터 동기화에 문제가 발생합니다.

 

문제를 방지하기 위해서는 mutations를 사용하는 게 좋겠죠. :) (참고! Mutations는 동기적으로 작업을 처리합니다.)

 

선언 방식

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

Vue.use(Vuex);

export default new Vuex.Store({
  state: {
    Users: [
      { id: 1, name: '라이언', address: 'Seoul' },
      { id: 2, name: '어피치', address: 'Seoul' },
      { id: 3, name: '네오', address: 'Seoul' },
      { id: 4, name: '무지', address: 'Seoul' },
    ],
  },
  getters: {
    Users: state => {
      return state.Users;
    }
  },
  mutations: {
    addUsers: (state, payload) => {
      state.Users.push(payload);
    }
  },
  actions: {
  },
  modules: {
  },
});

 

mutations 선언 방식은 아래와 같습니다.

  mutations: {
    addUsers: (state, payload) => {
      state.Users.push(payload);
    }
  },

함수명을 정의합니다. 첫 번째 파라미터는 state, 두 번째 파라미터는 payload(실제 데이터)입니다.

payload는 addUsers를 호출했을 때 담겨오는 데이터입니다.

 

호출 방식

 

<template lang='ko'>
    <div>
        Home Component
        <ul>
          <li v-for="user in Users">
            {{ user.name }}
          </li>
        </ul>
        <button @click="addUser">유저 등록</button>
    </div>
</template>
<script>

// mapMutations import
import { mapGetters, mapMutations } from "vuex";

export default {
  name: "HomeComponent",
  data() {
    return {
      user: { id: 4, name: "개발이 취미인 사람", address: "Seoul" },
    };
  },
  created() {
    console.log("유저 목록 : ", this.Users);
  },
  computed: {
    ...mapGetters(["Users"]),
    ...mapGetters({ Users: "Users" }),
  },
  methods: {
  
    // mapMutations 함수 등록
    ...mapMutations(["addUsers"]),
    ...mapMutations({ addUsers: "addUsers" }),
    
    addUser() {
      //Mutations 함수 사용
      this.addUsers(this.user);
      this.$store.commit("addUsers", this.user);
    },
  },
};
</script>
<style lang='scss'>
</style>

호출 방식은 두 가지가 있습니다.

 

mapMutations를 import 하고 methods 부분에 함수를 등록합니다. 그리고 this.addUsers()를 통해 호출해서 사용하면 됩니다. 

직접 호출은 this.$store.commit("선언한 mutations 함수명", payload 값)입니다.

 

- Actions (Mutations 데어터 전달)

Actions는 Mutations에 작업량이 몰리는 부분을 방지합니다. (참고! Actions는 비동기적으로 동작합니다.)

 

선언 방식

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

Vue.use(Vuex);

export default new Vuex.Store({
  state: {
    Users: [
      { id: 1, name: '라이언', address: 'Seoul' },
      { id: 2, name: '어피치', address: 'Seoul' },
      { id: 3, name: '네오', address: 'Seoul' },
      { id: 4, name: '무지', address: 'Seoul' },
    ],
  },
  getters: {
    Users: state => {
      return state.Users;
    }
  },
  mutations: {
    addUsers: (state, payload) => {
      state.Users.push(payload);
    }
  },
  actions: {
    addUsers: ({ commit, state }, payload) => {
      console.log(state);
      commit('addUsers', payload);
    },
  },
  modules: {
  },
});

 

Actions 선언 방식은 아래와 같습니다.

  actions: {
    addUsers: ({ commit, state }, payload) => {
      console.log(state);
      commit('addUsers', payload);
    },
  },

 

함수명을 정의합니다. 첫 번째 파라미터에는 Context 객체입니다. ES6 구조 분해를 통해 사용할 commit, state 값을 가져옵니다.

두 번째 파라미터 payload는 addUsers를 호출했을 때 담겨오는 "전달받은 데데"입니다.

 

 

호출 방식

<template lang='ko'>
    <div>
        Home Component
        <ul>
          <!--  -->
          <li v-for="user in Users">
            {{ user.name }}
          </li>
        </ul>
        <button @click="addUser">유저 등록</button>
    </div>
</template>
<script>

//mapActions import
import { mapGetters, mapActions } from "vuex";

export default {
  name: "HomeComponent",
  data() {
    return {
      user: { id: 4, name: "개발이 취미인 사람", address: "Seoul" },
    };
  },
  created() {
    console.log("유저 목록 : ", this.Users);
  },
  computed: {
    ...mapGetters(["Users"]),
    ...mapGetters({ Users: "Users" }),
  },
  methods: {
    
    // mapActions 등록
    ...mapActions(["addUsers"]),
    ...mapActions({ addUsers: "addUsers" }),
    addUser() {
    
      //mapActions 함수 사용
      this.addUsers(this.user);
      this.$store.dispatch("addUsers", this.user);
    },
  },
};
</script>
<style lang='scss'>
</style>

 

호출 방식은 두 가지입니다.

mapActions를 통해 등록된 함수를 호출해서 사용하는 방식과 직접 $store로 접근하는 방식입니다.

편한 방식을 사용하시면 될 것 같습니다.

 

- Modules (상태 모듈화)

우리나 하나의 파일 안에 State, Getters, Mutations, Actions를 선언했습니다.

 

Modules는 하나의 파일 안에 모든 소스코드를 선언하는 방식이 아닌 vuex를 모듈별로 만들어 사용하는 방식을 제시합니다.

 

우리가 이때까지 만들었던 소스코드를 모듈화를 통해 정리해보겠습니다.

 

기존 소스코드

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

Vue.use(Vuex);

export default new Vuex.Store({
  state: {
    Users: [
      { id: 1, name: '라이언', address: 'Seoul' },
      { id: 2, name: '어피치', address: 'Seoul' },
      { id: 3, name: '네오', address: 'Seoul' },
      { id: 4, name: '무지', address: 'Seoul' },
    ],
  },
  getters: {
    Users: state => {
      return state.Users;
    }
  },
  mutations: {
    addUsers: (state, payload) => {
      state.Users.push(payload);
    }
  },
  actions: {
    addUsers: ({ commit, state }, payload) => {
      console.log(state);
      commit('addUsers', payload);
    },
  },
  modules: {
  },
});

 

store 폴더 아래에 modules 폴더를 생성하겠습니다.

그리고 users라는 js 파일을 만들고 기존에 있던 index.js 파일과 새로 생성한 users.js 파일을 수정하겠습니다.

 

폴더 구조

index.js 파일

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

// https://webpack.js.org/guides/dependency-management/#requirecontext
const modulesFiles = require.context("./modules", true, /\.js$/);
// you do not need `import app from './modules/app'`
// it will auto require all vuex module from modules file
const modules = modulesFiles.keys().reduce((modules, modulePath) => {
  // set './app.js' => 'app'
  const moduleName = modulePath.replace(/^\.\/(.*)\.\w+$/, "$1");
  const value = modulesFiles(modulePath);
  modules[moduleName] = value.default;
  return modules;
}, {});

Vue.use(Vuex);

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

 

modules/users.js 파일

const state = {
    Users: [
      { id: 1, name: '라이언', address: 'Seoul' },
      { id: 2, name: '어피치', address: 'Seoul' },
      { id: 3, name: '네오', address: 'Seoul' },
      { id: 4, name: '무지', address: 'Seoul' },
    ],
};

const getters = {
    Users: state => {
        return state.Users;
    }
};

const mutations = {
    addUsers: (state, payload) => {
        state.Users.push(payload);
    }
};

const actions = {
    addUsers: ({ commit, state }, payload) => {
        console.log(state);
        commit('addUsers', payload);
    }
};

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

 

기존 방식과 다르게 새로운 파일을 통해 State, Getters, Mutations, Actions를 정의하고 해당 객체를 내보내 주고 있습니다.

여기서 중요한 옵션은 namespaced 입니다. 무조건 true로 설정해야 해당 파일을 구분할 수 있습니다.

 

- 호출하기 

<template lang='ko'>
    <div>
        Home Component
        <ul>
          <!--  -->
          <li v-for="user in Users">
            {{ user.name }}
          </li>
        </ul>
        <button @click="addUser">유저 등록</button>
    </div>
</template>
<script>
import { mapGetters, mapActions } from "vuex";

export default {
  name: "HomeComponent",
  data() {
    return {
      user: { id: 4, name: "개발이 취미인 사람", address: "Seoul" },
    };
  },
  created() {
    console.log("유저 목록 : ", this.Users);
  },
  computed: {
    // ...mapGetters(["Users"]),
    ...mapGetters({ Users: "users/Users" }),
  },
  methods: {
    // ...mapActions(["addUsers"]),
    ...mapActions({ addUsers: "users/addUsers" }),
    addUser() {
      this.addUsers(this.user);
      this.$store.dispatch("users/addUsers", this.user);
    },
  },
};
</script>
<style lang='scss'>
</style>

 

사용하지 못하는 방식은 주석을 통해 그대로 뒀습니다.

 

위 코드를 볼 때 변경된 부분은 경로입니다. Users를 호출할 때는 users/Users로 파일/객체명으로 접근을 할 수 있습니다.

 

Vuex 개념은 쉬운 듯 어려운 듯 한 느낌을 들게하는 기술입니다.

 

차근차근 천천히 하나씩 따라하면서 익히는 걸 추천드립니다.