티스토리 뷰

(본 포스팅은 Vue 3타입스크립트를 사용하는 환경 기준으로 설명합니다)

 

 

 

안녕하세요

 

이번 포스팅에서는 Vue 3 버전부터 공식적으로 추가될 Composition API와

Vuex를 사용하는 방법에 대해 알아보려고 합니다.

 

 

기존에 사용하던 Vuex 저장소 접근 방식은 아래와 같이 바인딩된

this 컨텍스트의 $store를 통해 주입된 Vuex 저장소에 접근할 수 있었습니다.

// Vuex store
import vuex from 'vuex';

export default new Vuex.Store({
  state: {
    name: '근둥'
  },
})


// Component
export default {
  name: 'App',
  created () {
    console.log(this.$store.state.name); // 출력: 근둥
  },
}

 

 

하지만, 컴포지션 API에는 this 바인딩이 존재하기 않기 때문에

기존처럼 Vuex 저장소에 직접적으로 접근할 수 없습니다.

(Vuex 뿐만 아니라 vue-router도 해당)

 

 

 

vuex(vue-router) 버전이 4.0.0 버전 미만인 경우, 데이터 제공(Provide)/주입(Inject)을 통해 해결할 수 있습니다.

(버전 4.0.0 이상은 게시물 최하단 참고)

 

상위 컴포넌트에서 특정 데이터를 제공하고,

하위의 컴포넌트에서 필요한 데이터를 주입받아 동일한 데이터를 공유할 수 있습니다.

 

상위 컴포넌트에서 Vuex 저장소를 제공하면,

하위 컴포넌트들이 Vuex 저장소에 접근하여 사용할 수 있다는 의미입니다.

 

 

 

화면에서 + 버튼을 누르면 카운트가 1증가하고, -버튼을 누르면 카운트가 1 감소하는

예제 코드를 살펴보며 직접 구현해봅시다.

 

먼저, 아래와 같은 Vuex 저장소를 생성했다고 가정합니다.

(src/store/index.ts)

import { createStore, Store } from 'vuex';

interface RootState {
  count: number;
}

const initalState: RootState = {
  count: 0,
};

const store = createStore({
  state: initalState,
  mutations: {
    SET_COUNT(state, count) {
      state.count = count;
    },
  },
  actions: {
    INCREASE({ state, commit }) {
      commit('SET_COUNT', state.count + 1);
    },
    DECREASE({ state, commit }) {
      commit('SET_COUNT', state.count - 1);
    },
  },
});

카운트(count) 상태를 가지고 있고, 카운트를 1씩 증가(INCREASE)/감소(DECREASE)시키는 액션과,

상태값을 변이시키는 SET_COUNT 뮤테이션으로 구성되어있습니다.

 

 

화면을 담당하는 컴포넌트는 아래와 같습니다.

(src/App.vue)

<template>
  <div id="app">
    <Home/>
  </div>
</template>

<script lang="ts">
import { defineComponent } from 'vue';
import Home from '@/components/Home';

export default defineComponent({
  name: 'App',
  components: { Home },
  setup() {
    // TODO
  },
});
</script>

 

 

 

(src/components/Home.vue)

<template>
  <div class="home">
    <h2>{{ count }}</h2>
    <div>
      <button @click="increase">+</button>
      <button @click="decrease">-</button>
    </div>
  </div>
</template>

<script lang="ts">
import { defineComponent } from 'vue';

export default defineComponent({
  name: 'Home',
  setup() {
    const count = ???; // Vuex state: count

    const increase = () => ???; // Vuex action: INCREASE
    const decrease = () => ???; // Vuex action: DECREASE

    return { count, increase, decrease };
  },
});
</script>

위 컴포넌트에서 Vuex 저장소의 count 상태 값을 화면에 표시하고,

+ 버튼을 눌렀을 때 증가 액션을, - 버튼을 눌렀을 때 감소 액션을 호출하도록 구현하는 것이 우리의 목표입니다.

 

코드 구성을 확인해보면 아시겠지만, App.vue가 최상위 컴포넌트로 자리잡고 있습니다.

최상위 컴포넌트에서 Vuex 저장소를 제공(Provide)하면 하위의 모든 컴포넌트에서 주입(Inject)받아 사용할 수 있게 됩니다.

 

 

Vuex 저장소를 제공/주입할 수 있도록 아래와 같이 Vuex 코드를 수정합니다.

 

import { provide, inject } from 'vue';
import { createStore, Store } from 'vuex';

interface RootState {
  count: number;
}

const initalState: RootState = {
  count: 0,
};

const store = createStore({
  state: initalState,
  mutations: {
    SET_COUNT(state, count) {
      state.count = count;
    },
  },
  actions: {
    INCREASE({ state, commit }) {
      commit('SET_COUNT', state.count + 1);
    },
    DECREASE({ state, commit }) {
      commit('SET_COUNT', state.count - 1);
    },
  },
});

// Provide 구분 값
const StoreSymbol = Symbol();

// 저장소 제공 헬퍼 함수
export const provideStore = () => {
  provide(StoreSymbol, store);
};

// 저장소 주입 헬퍼 함수
export const useStore = () => {
  const store = inject<Store<RootState>>(StoreSymbol);
  if (!store) {
    throw new Error('No store provided');
  }
  return store;
};

export default store;

 

 

 

저장소 코드는 기존과 동일하며, 하단에 provideStore, useStore 헬퍼 함수가 추가로 구현되었습니다.

 

기존 Vue에서는 제공/주입 기능을 컴포넌트에서 사용할 수 있었지만,

제공/주입에 해당하는 컴포지션 API도 Vue 3에 함께 추가되었습니다.

 

컴포지션 API의 제공/주입은 provide와 inject 함수를 사용합니다.

 

 

 

먼저, Vuex 저장소를 제공하는 provideStore 헬퍼 함수는 아주 간단합니다.

생성한 Vuex 저장소 객체를 provide 함수를 통해 제공하도록 구현되어있는 것이 전부입니다.

 

provide 함수는 첫 번째 인자로 제공 대상에 대한 구분 값을 받으며, 두 번째 인자로는 제공할 데이터를 받습니다.

구분 값은 키 값이라고 볼 수 있으며, 이후 주입받을 때 해당 키 값에 해당하는 제공 데이터를 주입 받습니다.

 

 

 

useStore 헬퍼 함수는 반대로 제공하는 데이터를 주입받기 위해 inject 함수를 통해 데이터를 가져옵니다.

inject는 첫 번재 인자로 주입받을 대상의 구분 값을 받으며,

두 번째 인자로는 주입받을 대상이 없을 경우 사용할 기본 값을 받습니다 (Optional)

 

제공할 때의 구분 값을 StoreSymbol 값을 사용했기 때문에

주입할 때에도 동일한 값을 사용하여 Vuex 저장소 객체를 가져올 수 있습니다.

 

 

 

이렇게 구현한 헬퍼 함수는 컴포넌트에서 아래와 같이 사용할 수 있습니다.

 

(src/App.vue)

<template>
  <div id="app">
    <router-view />
  </div>
</template>

<script lang="ts">
import { defineComponent } from 'vue';
import { provideStore } from '@/store';

export default defineComponent({
  name: 'App',
  setup() {
    provideStore(); // Vuex 저장소 제공
  },
});
</script>

최상위 컴포넌트에서 Vuex 저장소를 제공하도록 provideStore 헬퍼 함수를 사용했습니다.

 

한 번 하위 컴포넌트에서 제공한 저장소를 주입받아 사용해봅시다.

 

 

(src/components/Home.vue)

<template>
  <div class="home">
    <h2>{{ count }}</h2>
    <div>
      <button @click="increase">+</button>
      <button @click="decrease">-</button>
    </div>
  </div>
</template>

<script lang="ts">
import { defineComponent, computed } from 'vue';
import { useStore } from '@/store';

export default defineComponent({
  name: 'Home',
  setup() {
    const { state, dispatch } = useStore(); // 저장소 객체 주입받아 사용하기
    const count = computed(() => state.count); // 저장소 count 상태 값

    const increase = () => dispatch('INCREASE'); // INCREASE 액션 호출
    const decrease = () => dispatch('DECREASE'); // DECREASE 액션 호출

    return { count, increase, decrease };
  },
});
</script>

useStore 헬퍼 함수를 통해 제공한 Vuex 저장소 객체를 주입받습니다.

Vue 2 버전의 this.$store와 동일하게 저장소의 상태와 액션, 변이 등에 접근하여 사용할 수 있습니다.

 

저장소의 count 상태를 반응형으로 화면에 렌더링 하기 위해 computed를 사용하고,

+, - 버튼이 눌렸을 때 카운트 상태를 변경하기 위해 INCREASE, DECREASE 액션을 디스패치 하도록 코드가 구현되었습니다.

 

 

기능을 테스트해보면, 아래와 같이 완벽하게 동작합니다!

 

본 포스팅에서는 Vuex 기준으로 작성했지만,

vue-router도 동일한 방법으로 제공/주입하도록 구현할 수 있습니다.

 


vuex 4 및 vue-router 4 with Composition API

 

현재 시점으로는 아직 베타버전 이지만, 위에서 구현했던 헬퍼 함수들을 제공합니다.

Vue.js 3 정식 릴리즈와 함께 버전 4들도 안정화되어 제공될 것이라고 생각합니다.

 

import { useStore } from 'vuex';
import { useRoute, useRouter } from 'vue-router';

// setup
const store = useStore();
const route = useRoute();
const router = useRouter();

 

 

4 버전 부터는, 앞서 구현했던 것과 동일한 기능을 수행하는 헬퍼 함수를 자체적으로 제공합니다.

다만 위 방식을 사용하는 경우, 타입추론이 제대로 이루어지지 않는다는 단점이 존재합니다. (any)

 

import { useStore } from 'vuex';

const store = useStore(); // Store<any>
store.state. // 추론 불가...

위의 경우 선언한 Vuex 저장소 타입으로 직접 단언하여 타입을 명시하거나,

명확한 타입의 저장소/라우터를 제공하기 위해 직접 제공/주입하도록

구현할 수 있으니 적절히 사용하면 될 것 같습니다.

 


 

준비한 포스트는 여기까지이며,

다음에 더 다양한 내용으로 찾아뵙겠습니다.

 

감사합니다!

 

 

 

[참고 자료]

composition-api.vuejs.org/#plugin-development

v3.vuejs.org/guide/composition-api-provide-inject.html

 

 

 

댓글
댓글쓰기 폼