Vuex 제대로 사용하는 방법 (Vuex의 기초부터 심화까지)

남양주개발자

·

2020. 9. 13. 21:56

728x90
반응형

Vuex 란?

vuex는 vue.js 애플리케이션을 위한 상태 관리 라이브러리입니다. 모든 컴포넌트들에서 접근 가능한 중앙 집중식 데이터 저장소입니다. 모든 데이터 흐름을 중앙에서 관리함으로써 예측 가능한 애플리케이션을 구현할 수 있습니다. Vue의 공식 devtools 확장프로그램과 통합해서 사용하면 훨씬 사용하기 쉬워집니다.

시작하기 (Vuex 설치 방법)

vue-cli를 사용해서 vue 프로젝트를 세팅합니다. 

vue create vuex-test

vue 프로젝트를 세팅했다면 vuex 라이브러리를 설치해주세요.

yarn add vuex
// or
npm install vuex --save

Nuxt에서는 기본적으로 Vuex를 지원함으로 따로 설치할 필요가 없습니다!

 

Vuex Store

Nuxt.js가 Vuex를 구현하는 핵심 이유는, 저장소를 사용하여 상태를 관리하는 것은 모든 대형 애플리케이션에서 중요하기 때문입니다.

ko.nuxtjs.org

Vuex 스토어 연동

Vue 애플리케이션에서 Vuex 스토어를 사용하기 위해 전역 스토어를 생성하고 전역 Vue 인스턴스에 스토어를 주입시킵니다. 루트 인스턴스에 store 옵션을 제공함으로써 저장소는 루트의 모든 하위 컴포넌트에 주입되고 this.$store로 스토어를 사용할 수 있습니다.

// main.js
import Vue from 'vue'
import App from './App.vue'
import Vuex from 'vuex'
Vue.use(Vuex)

// 예시를 들기 위한 전역 스토어 객체
const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    increment (state) {
      state.count++
    }
  }
})

Vue.config.productionTip = false

new Vue({
  store, // store를 전역으로 등록
  render: h => h(App),
}).$mount('#app')

 

자! 이제 Vuex를 학습하기 위한 사전작업이 끝났습니다. 이제 Vuex의 기본 흐름과 핵심컨셉에 대해서 다뤄봅시다.

Vuex의 기본흐름과 핵심컨셉

아래 이미지는 Vuex의 기본 흐름을 공부하려고 할 때 가장 많이 접하는 이미지입니다. 처음 Vuex를 접할 때 아래 이미지를 보고 이해하는 것은 쉽지 않은 일입니다. 아래 이미지에서 설명하는 개념을 이해하기 쉽게 설명해드릴 테니 걱정 마세요.

Vuex 라이브러리의 기본흐름, 컨셉

아래 3가지 핵심컨셉만 이해하면 위 이미지에서 설명하고 있는 Vuex의 핵심컨셉을 쉽게 이해할 수 있습니다.

  • 상태(State)
  • 변이(Mutations)
  • 액션(Actions)

상태(State)

Vuex는 단일 상태 트리(단일 객체)를 사용합니다. 단일 상태 트리(단일 객체)를 어렵게 생각하지 마시고, 애플리케이션에서 사용하는 하나의 원본 데이터라고 생각하시면 됩니다. 애플리케이션의 모든 컴포넌트들은 데이터가 필요할 때, 이 원본 데이터를 꺼내서 사용합니다.

단일 상태 트리를 사용했을 때의 장점은 애플리케이션에서 사용하고 있는 데이터의 흐름을 추적하기 매우 쉽다는 것입니다. 데이터의 흐름을 추적할 수 있으니 언제 어떤 데이터가 변이 했는지 알 수 있고, 덕분에 시간 여행 디버깅(time travel debugging)을 할 수 있습니다.

State를 Vue 컴포넌트에서 사용하기

Vue 컴포넌트에서 Vuex 스토어에 있는 state를 가져오는 방법은 정말 간단합니다. 아래와 같이 Vuex 스토어의 state에 접근해서 Vue 컴포넌트의 계산된 속성(computed)에 주입만 시켜주면 됩니다.

<script>
export default {
  name: 'App',
  computed: {
    count() {
      return this.$store.state.count // store count 상태값
    }
  }
}
</script>

this.$store.state.count의 상태 값이 변경되면 자동으로 계산된 속성이 자동으로 변경되고 관련 DOM 업데이트가 될 것입니다.

변이(Mutations)

변이(Mutations)는 Vuex 스토어(Store)에 있는 상태(State) 값을 변경하는 유일한 방법입니다. 변이를 사용하는 방법은 아래와 같습니다.

1. 변이 핸들러 생성

const store = new Vuex.Store({
  state: {
    count: 0,
  },
  mutations: {
  	// count 상태 값을 증가시키는 변이
    increment(state) {
      state.count++;
    },
  	// count 상태 값을 감소시키는 변이
    decrement(state) {
      state.count--;
    },
  },
});

2. 스토어(store)의 commit 메서드로 변이 핸들러 호출

변이 핸들러는 직접 호출하지 않고 스토어(store)의 commit 메서드를 사용해서 호출합니다. commit 메서드를 사용해서 변이 핸들러를 호출하는 방법은 아래와 같습니다.

this.$store.commit('increment') // 변이 핸들러 호출

Vue 컴포넌트에서 스토어의 commit 메서드를 사용해서 변이 핸들러를 호출할 수 있습니다.

// 컴포넌트에서 store commit을 활용한 예시
<template>
  <div id="app">
    <button @click="$store.commit('increment')">증가</button>
    <button @click="$store.commit('decrement')">감소</button>
    {{ count }}
  </div>
</template>

<script>
export default {
  name: "App",
  computed: {
    count() {
      return this.$store.state.count;
    },
  },
};
</script>

변이 핸들러를 사용해서 vuex 상태 값을 변경하는 예시

커밋(commit)은 2가지 방법으로 실행할 수 있습니다.

페이로드(payload)를 가진 커밋(commit)

commit 메서드를 실행할 때 페이로드(payload)라고 불리는 값을 전달할 수 있습니다. 페이로드는 상태 값을 변이 시킬 때 사용될 수 있습니다.

...
mutations: {
  increment(state, n = 1) {
    state.count += n;
  },
 }
// Vue 컴포넌트
<button @click="$store.commit('increment', 3)">증가</button>

commit에 페이로드를 전달한 값으로 상태 값을 변이시키는 예시

대부분의 경우 페이로드(payload)는 여러 필드를 포함할 수 있는 객체(Object)로 전달됩니다.

// ...
mutations: {
  increment (state, payload) {
    state.count += payload.amount
  }
}
this.$store.commit('increment', {
  amount: 10
})

객체(Object) 스타일 커밋(commit)

변이를 커밋하는 또 다른 방법은 type 속성을 가진 객체를 직접 사용하는 것입니다.

this.$store.commit({
  type: 'increment',
  amount: 10
})

객체 스타일 커밋을 사용할 때 전체 객체는 변이 핸들러에 페이로드로 전달되므로 핸들러는 동일하게 유지됩니다.

mutations: {
  increment(state, payload) {
    state.count += payload.amount;
  }
}
// Vue 컴포넌트에서 사용 예시
<button @click="$store.commit({ type: 'increment', amount: 3 })">
  증가
</button>

객체 스타일 커밋을 사용할 때 페이로드 객체 예시

❗️변이(Mutation)의 몇 가지 중요한 팁❗️

Vue 반응성(Reactivity) 규칙을 따라야 합니다.

Vuex 스토어의 상태 값은 Vue에 의해 반응하므로, 상태 값을 변경하면 상태 값을 관찰하는 Vue 컴포넌트들이 자동으로 업데이트됩니다.

  1. 변경 감지할 데이터를 미리 초기화합니다.
  2. 만약 객체에 새 속성을 추가할 경우 다음 중 하나를 수행해야 됩니다.

Vue.set(object, key, value) 메소드를 사용하여 중첩된 객체에 반응성 속성을 추가합니다.

Vue.set(obj, 'newProp', 123)

객체를 새로운 객체로 교체합니다. 예를 들어, 객체 전개 구문(ES6 spread operator)을 사용하면 아래와 같이 작성할 수 있습니다.

state.obj = { ...state.obj, newProp: 123 }

반응성 예시

아래 코드는 countObj 객체에 a의 값은 미리 초기화를 했지만, b의 값은 컴포넌트의 마운트(mounted) 라이프사이클 시점에 객체에 주입한 것을 알 수 있습니다.

<template>
  <div id="app">
    <p>a: {{ countObj.a }}</p>
    <p>b: {{ countObj.b }}</p>
    <button @click="increment('a')">a 숫자 증가</button>
    <button @click="increment('b')">b 숫자 증가</button>
  </div>
</template>

<script>
export default {
  name: "App",
  data() {
    return {
      countObj: {
        a: 0,
      },
    };
  },
  mounted() {
    this.countObj["b"] = 0;
  },
  methods: {
    increment(type) {
      this.countObj[type]++;
    },
  },
};
</script>

위 코드의 동작은 아래와 같습니다. countObj의 a 프로퍼티는 정상적으로 countObj의 a 프로퍼티의 값이 정상적으로 변경되었지만, countObj의 b 프로퍼티는 값이 변경되지 않습니다.

정상적으로 Vue 컴포넌트가 업데이트되지 않는 모습

Vm.$set 인스턴스 메소드를 사용해서 동적으로 객체에 반응성 속성을 추가해봅시다.

export default {
  ...
  mounted() {
    this.$set(this.countObj, "b", 0);
  }
};

객체에 동적으로 반응성 속성을 추가하고 컴포넌트가 제대로 업데이트되는 모습입니다.

객체에 반응성 속성을 추가한 모습

이렇게 Vue의 반응성 규칙을 이해하는 것은 굉장히 중요합니다. Vuex를 제대로 사용하기 위해서는 이 반응성 규칙에 대해서 정확히 짚고 넘어가시기 바랍니다.

변이 타입(Mutation Type)으로 상수 사용을 지향합니다.

변이(Mutation) 타입에 상수를 사용하는 것은 일반적인 패턴입니다. 상수를 하나의 파일에 저장하면 같이 작업하는 팀원들이 전체 애플리케이션에서 어떤 변이가 가능한지 한눈에 파악할 수 있습니다.

// mutation-types.js
export const SOME_MUTATION = 'SOME_MUTATION'
// store.js
import Vuex from 'vuex'
import { SOME_MUTATION } from './mutation-types'

const store = new Vuex.Store({
  state: { ... },
  mutations: {
    [SOME_MUTATION] (state) {
      // 변이 상태
    }
  }
})

상수를 사용할지 여부는 선택사항이므로 팀에 따라서 어떻게 할지 정책적으로 결정해서 가져가면 됩니다. 하지만 리액트의 리덕스(Redux)에서도 타입을 상수로 사용함으로써 코드를 깨끗하게 유지하기 위해 노력하고 있습니다. 

변이는 무조건 동기적이어야 합니다.

변이의 필수적인 규칙은 변이 핸들러 함수동기적 이어야 한다는 것입니다. 비동기 흐름 처리는 바로 다음에 소개할 액션(Action)에서 처리하시기 바랍니다.

액션(Actions)

액션은 액션 자체로 상태 값들을 변이 시키는 것이 아닌 모든 과정이 끝났을 때 변이에 대한 커밋(Commit)을 시킵니다. 커밋이란 단어의 의미 그대로 변경을 확정 짓는 일을 커밋이라고 합니다. 그래서 액션에서 모든 비동기 처리를 마치고 변이를 커밋함으로써 Vuex 스토어의 데이터를 업데이트합니다.

예시

아래는 액션과 변이를 적절하게 활용해서 비동기 흐름을 처리하고 데이터를 업데이트하는 예시입니다. 예시는 유저 데이터를 비동기로 가져오고 유저 데이터에 포함되어 있는 유저의 고유 id값으로 유저의 포스트 데이터를 가져와서 화면에 노출시키는 상황입니다.

// delay helper
const delay = (duration = 500) => {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve();
    }, duration);
  });
};

// 포스트 데이터를 가져오는 호출 예시 (비동기 호출)
const requestPostData = async (id = 0) => {
  await delay(1500);

  return [
    {
      id: 1,
      title: "post title 1",
      content: "post content 1",
    },
  ];
};

// 유저 데이터를 가져오는 호출 예시 (비동기 호출)
const requestUserData = async () => {
  await delay(1500);

  return {
    id: 1317,
    name: "박경두",
    age: 30,
    url: "https://github.com/ruden91",
  };
};

const store = new Vuex.Store({
  state: {
    isLoading: false,
    posts: [],
  },
  mutations: {
    CHANGE_LOAD_STATUS(state, status = true) {
      state.isLoading = status;
    },
    SET_POST_DATA(state, posts) {
      state.posts = posts;
    },
  },
  actions: {
    async getUserPostData({ commit }) {
      try {
        commit("CHANGE_LOAD_STATUS"); // 로딩처리 true
        const { id } = await requestUserData(); // 유저데이터를 가져온다.
        const posts = await requestPostData(id); // 유저데이터의 id값으로 포스트 목록을 가져온다.

        commit("SET_POST_DATA", posts); // 포스트 데이터를 변이시키기 위해 커밋 메서드의 인자값으로 전달한다.
      } catch (err) {
	    // 예외가 발생 시 여기에 로직을 추가할 수 있다.
        console.error(err);
      } finally {
        commit("CHANGE_LOAD_STATUS", false); // 로딩완료 처리 false
      }
    },
  },
});

Vue 컴포넌트에서는 store.dispatch('getUserPostData')를 실행만 하고, 모든 비즈니스 로직들은 Vuex에서 처리를 담당하고 있는 것을 알 수 있습니다. 철저히 Vue 컴포넌트에서는 데이터를 화면에 보여주는 역할을 하고 있다는 것을 알 수 있습니다. 이럴 경우의 장점은 단순 UI를 담당하는 부분(Presentation Component)과 데이터를 처리하는 부분(Container Component)이 나눠짐으로써 관심사의 분리가 되고, 코드의 유지보수가 굉장히 쉬워지죠.

// Vue 컴포넌트에서 사용 예시
<template>
  <div id="app">
    <p v-if="isLoading">post 데이터를 가져오는 중입니다...</p>
    <ul v-else>
      <li v-for="post in posts" :key="post.id">{{ post.title}}</li>
    </ul>
  </div>
</template>

<script>
export default {
  name: "App",
  mounted() {
    this.$store.dispatch("getUserPostData");
  },
  computed: {
    isLoading() {
      return this.$store.state.isLoading;
    },
    posts() {
      return this.$store.state.posts;
    },
  },
};
</script>

깔끔하게 비동기 흐름을 처리하는 모습

게터(Getters)

핵심 컨셉을 3가지(상태, 변이, 액션)라고 소개했는데 갑자기 나타난 게터(Getters)라는 녀석은 또 뭘까요? 너무 어렵게 생각하지 않으셔도 됩니다. Getters는 Vue 컴포넌트의 computed(계산된 속성)이랑 똑같습니다. 직접 수정하는 값이 아닌 상태 값을 활용해서 계산된 속성으로 사용됩니다.

위 예시에서 post 데이터를 가져와서 상태 값으로 저장한 것 기억나시나요? 우리는 posts 상태 값을 활용해서 포스트 개수를 표현하는 postCount 계산된 속성 값을 추가할 것입니다. 추가하는 방법은 아래와 같습니다.

const store = new Vuex.Store({
  ...
  getters: {
    postCount(state) {
      return state.posts.length;
    },
  }
});

정말 간단하죠? getters에서는 이렇게 state에 접근해서 계산된 속성을 만들 수 있습니다. Vue 컴포넌트에서 계산된 속성은 상태 값이 변경되면 자동으로 계산된 속성들은 업데이트되는 것 알고 계시죠? Vuex의 Getters도 마찬가지입니다. state 값이 변경되면 자동으로 getter는 변경됩니다.

심화과정

모듈화 (Module)

여기까지 우리는 Vuex의 핵심 컨셉인 상태(State), 변이(Mutation), 액션(Action)을 다뤄봤습니다. 하지만 프로젝트가 커지면 커질수록 이렇게 Vuex 스토어를 단일로 관리하면 정말 힘들 것 같지 않나요? 예를 들어, 프로젝트에 사용자 인증, 결제, 상품 목록들을 관리하는 데이터가 있다고 가정한다면 이 데이터들을 한 군데에서 모두 관리하면 어떻게 될까요? 복잡도는 굉장히 높아질 것이고, 유지 보수하기도 굉장히 어려워질 것입니다. 이를 해결하기 위해 Vuex는 모듈(Module)이라는 것을 제공해줍니다.

Vuex 단일 스토어를 모듈 단위로 쪼갤 수 있습니다. 각 모듈은 자체 상태, 변이, 액션, 게터를 포함할 수 있습니다. 모듈의 예시는 아래와 같습니다.

const moduleA = {
  state: () => ({ ... }),
  mutations: { ... },
  actions: { ... },
  getters: { ... }
}

const moduleB = {
  state: () => ({ ... }),
  mutations: { ... },
  actions: { ... }
}

const store = new Vuex.Store({
  modules: {
    a: moduleA,
    b: moduleB
  }
})

하지만 위처럼 구성하는 경우는 없고 보통 모듈은 파일로 관리하는 편입니다. 모듈 단위로 스토어를 관리하기 위해 위에서 작성한 코드를 기준으로 리팩터링을 해봅시다!

우선 main.js입니다. 굉장히 코드가 많이 줄어들었죠? 보통 프로젝트에서 Vuex 스토어를 붙일 때는 store를 따로 관리하곤 합니다.

// main.js
import Vue from "vue";
import store from "./store"; // store를 가져옵니다.
import App from "./App.vue";

Vue.config.productionTip = false;

new Vue({
  store,
  render: (h) => h(App),
}).$mount("#app");

아래는 기존에 Vuex 코드를 store/index.js로 옮긴 코드입니다. 아직 조금 아쉽죠? 기존에 Post 데이터를 가져오는 로직들은 모듈로 분리해보겠습니다.

// ./store/index.js
import Vuex from "vuex";
import Vue from "vue";
Vue.use(Vuex);

const delay = (duration = 500) => {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve();
    }, duration);
  });
};

const requestPostData = async (id = 0) => {
  await delay(1500);

  return [
    {
      id: 1,
      title: "post title 1",
      content: "post content 1",
    },
    {
      id: 2,
      title: "post title 2",
      content: "post content 2",
    },
    {
      id: 3,
      title: "post title 3",
      content: "post content 3",
    },
  ];
};

const requestUserData = async () => {
  await delay(1500);

  return {
    id: 1317,
    name: "박경두",
    age: 30,
    url: "https://github.com/ruden91",
  };
};

export default new Vuex.Store({
  state: {
    isLoading: false,
    posts: [],
  },
  getters: {
    postCount(state) {
      return state.posts.length;
    },
  },
  mutations: {
    CHANGE_LOAD_STATUS(state, status = true) {
      state.isLoading = status;
    },
    SET_POST_DATA(state, posts) {
      state.posts = posts;
    },
  },
  actions: {
    async getUserPostData({ commit }) {
      try {
        commit("CHANGE_LOAD_STATUS");
        const { id } = await requestUserData(); // 유저데이터를 가져온다.
        const posts = await requestPostData(id); // 유저데이터의 id값으로 포스트 목록을 가져온다.

        commit("SET_POST_DATA", posts);
      } catch (err) {
        console.error(err);
      } finally {
        commit("CHANGE_LOAD_STATUS", false);
      }
    },
  },
});

위에 Vuex root store에 존재하던 post 로직을 모듈로 분리했습니다. 기존에 데이터를 가져오던 로직인 requestPostData와 requestUserData는 api 폴더로 이동시켰습니다. 저희가 핵심 컨셉으로 다뤄봤던 상태(state), 액션(actions), 변이(mutations)만 알고 있다면 어려울 것 없습니다! 단순히 파일만 분리했을 뿐입니다.

// ./store/modules/post.js
import { requestPostData, requestUserData } from "../../api/post";

// initial state
const state = () => ({
  isLoading: false,
  posts: [],
});

// getters
const getters = {
  postCount(state) {
    return state.posts.length;
  },
};

// mutations
const mutations = {
  CHANGE_LOAD_STATUS(state, status = true) {
    state.isLoading = status;
  },
  SET_POST_DATA(state, posts) {
    state.posts = posts;
  },
};

// actions
const actions = {
  async getUserPostData({ commit }) {
    try {
      commit("CHANGE_LOAD_STATUS");
      const { id } = await requestUserData(); // 유저데이터를 가져온다.
      const posts = await requestPostData(id); // 유저데이터의 id값으로 포스트 목록을 가져온다.

      commit("SET_POST_DATA", posts);
    } catch (err) {
      console.error(err);
    } finally {
      commit("CHANGE_LOAD_STATUS", false);
    }
  },
};

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

스토어 모듈의 핵심은 아래 namespaced에 있습니다. 모듈이 독립적으로 사용되기를 원하시면 namespaced: true라고 네임스페이스에 명시하면 됩니다.

export default {
  namespaced: true, // 스토어 모듈의 핵심
  state,
  getters,
  actions,
  mutations,
};
// store/index.js
import Vuex from "vuex";
import post from "./modules/post";
import Vue from "vue";

Vue.use(Vuex);

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

네임스페이스 모듈 내부에서 다른 모듈이나 전역 데이터에 접근하려면?

만약 네임스페이스 모듈 내부에서 전역이나 다른 모듈의 state와 getters에 접근하고 싶다면 rootState와 rootGetters를 활용하면 됩니다.

// getters
// `getters`는 해당 모듈의 지역화된 getters
// getters의 4번째 인자를 통해서 rootGetters 사용 가능
const getters = {
  postCount(state, getters, rootState, rootGetters) {
    return state.posts.length;
  },
};

// actions
const actions = {
  // rootState, rootGetters를 통해 최상단 스토어 영역의 state와 getters에 접근 가능하다.
  // 여기서 dispatch, commit, getters, state는 지역 스토어 영역
  async getUserPostData({ dispatch, commit, getters, rootGetters, rootState, state }) {
    try {
      commit("CHANGE_LOAD_STATUS");
      const { id } = await requestUserData(); // 유저데이터를 가져온다.
      const posts = await requestPostData(id); // 유저데이터의 id값으로 포스트 목록을 가져온다.

      commit("SET_POST_DATA", posts);
    } catch (err) {
      console.error(err);
    } finally {
      commit("CHANGE_LOAD_STATUS", false);
    }
  },
};

예를 들어 우리는 comment라는 스토어를 하나 만들었다고 가정해봅시다.

/* eslint-disable no-unused-vars */

// initial state
const state = () => ({
  comments: [],
});

// getters
// `getters`는 해당 모듈의 지역화된 getters
// getters의 4번째 인자를 통해서 rootGetters 사용 가능
const getters = {
  commentCount(state) {
    return state.comments.length;
  },
};

// mutations
const mutations = {
  CHANGE_LOAD_STATUS(state, status = true) {
    state.isLoading = status;
  },
  SET_COMMENT_DATA(state, comments) {
    console.log("SET_COMMENT_DATA");
    state.comments = [
      {
        id: 1,
        name: "comment 1",
        content: "comment content 1",
      },
    ];
  },
};

// actions
const actions = {
  setCommentData({ commit }) {
    commit("SET_COMMENT_DATA");
  },
};

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

스토어 구조는 아래와 같습니다. modules폴더에 comment 모듈 스토어를 추가했습니다.

 

comment 모듈 스토어를 추가한 모습

import Vuex from "vuex";
import post from "./modules/post";
import comment from "./modules/comment"; // comment 모듈 추가
import Vue from "vue";

Vue.use(Vuex);

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

우리는 post 모듈 스토어에서 comment 모듈 스토어에 있는 액션과 변이를 사용해볼 것입니다. 다시 post 모듈로 돌아가 봅시다. post 모듈 스토어의 getUserPostData 액션에서 comment 모듈 스토어의 액션인 setCommentData를 실행합니다. 하지만 여기서 우리는 알아야 될 점은 다른 모듈에 접근하기 위해서는 dispatch에 root: true를 사용해야 된다는 것입니다.

// actions
const actions = {
  async getUserPostData({
    dispatch,
    commit,
    getters,
    rootGetters,
    rootState,
    state,
  }) {
    dispatch("comment/setCommentData", null, { root: true }); // comment setCommentData 액션함수 실행
	...
  },
};

정상적으로 comment 모듈 스토어의 액션이 실행된 모습

커밋(commit) 또한 dispatch와 동일합니다. 아래와 같이 root: true를 넣어주면 됩니다.

commit('comment/SET_COMMENT_DATA', null, { root: true }) // -> 'SET_COMMENT_DATA'

컴포넌트에 모듈을 바인딩하는 방법

컴포넌트에 우리가 작성한 모듈 데이터를 바인딩하는 방법은 굉장히 간단합니다. 우리는 vuex 라이브러리에서 제공하는 mapState, mapGetters, mapActions 헬퍼를 createNamespacedHelpers와 함께 사용하여 네임스페이스 헬퍼를 생성할 수 있습니다.

import { createNamespacedHelpers } from "vuex";
const { mapState, mapGetters, mapActions } = createNamespacedHelpers("post");
// App.vue 컴포넌트
<template>
  <div id="app">
    <p v-if="isLoading">post 데이터를 가져오는 중입니다...</p>
    <template v-else>
      <ul>
        <li v-for="post in posts" :key="post.id">{{ post.title}}</li>
      </ul>
      <p>{{ postCount }}개</p>
    </template>
  </div>
</template>

<script>
import { createNamespacedHelpers } from "vuex";
const { mapState, mapGetters, mapActions } = createNamespacedHelpers("post");
export default {
  name: "App",
  mounted() {
    this.getUserPostData();
  },
  computed: {
    ...mapState(["isLoading", "posts"]),
    ...mapGetters(["postCount"]),
  },
  methods: {
    ...mapActions(["getUserPostData"]),
  },
};
</script>

만약 comment 모듈의 네임스페이스 헬퍼를 같이 사용하고 싶다면 아래와 같이 사용하면 됩니다.

<template>
  <div id="app">
    <p v-if="isLoading">post 데이터를 가져오는 중입니다...</p>
    <template v-else>
      <ul>
        <li v-for="post in posts" :key="post.id">{{ post.title}}</li>
      </ul>
      <p>{{ postCount }}개</p>
    </template>
  </div>
</template>

<script>
import { createNamespacedHelpers } from "vuex";
const { mapState, mapGetters, mapActions } = createNamespacedHelpers("post");
const { mapState: mapCommentState } = createNamespacedHelpers("comment");
export default {
  ...
  computed: {
    ...mapCommentState(["comments"]),
    ...mapState(["isLoading", "posts"]),
    ...mapGetters(["postCount"]),
  },
  methods: {
    ...mapActions(["getUserPostData"]),
  },
};
</script>

Strict 모드

strict 모드를 사용하기 위해, strict: true를 Vuex 저장소를 만들 때 추가하면 됩니다.

const store = new Vuex.Store({
  // ...
  strict: true
})

strict 모드에서는 Vuex 상태가 변이 핸들러 외부에서 변이 될 때 마다 오류가 발생합니다. 이렇게하면 디버깅 도구로 모든 상태 변이를 명시적으로 추적 할 수 있습니다.

배포시 strict 모드를 절대 켜지 마세요! Strict 모드는 부적절한 변이를 감지하기 위해 상태 트리를 자세히 관찰합니다. 성능 이슈를 피하기 위해 배포 환경에서 이를 해제해주세요. 아래와 같이 process.env.NODE_ENV 환경변수를 활용해서 strict 모드를 제어할 수 있습니다.

const store = new Vuex.Store({
  // ...
  strict: process.env.NODE_ENV !== 'production'
})

마무리

Vuex의 기본적인 핵심컨셉인 상태(State), 변이(Mutation), 액션(Action)에 대해서 살펴봤고, 기본적으로 프로젝트를 세팅할 때 Vuex 스토어를 세팅하는 방법, 그리고 확장을 위해 스토어를 모듈화 하는 방법까지 다뤄봤습니다. 이번에 소개해드린 Vuex의 핵심컨셉, Vue의 반응성 규칙 그리고 Vuex를 확장하기 위한 모듈에 대해서 이해하셨다면 충분히 Vuex를 잘 활용할 수 있다고 생각합니다.

 

728x90
반응형
그리드형

이 포스팅은 쿠팡파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.

💖 저자에게 암호화폐로 후원하기 💖

아이콘을 클릭하면 지갑 주소가자동으로 복사됩니다