ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Vue 3] Composition API와 Vuex 사용하기
    Vue.js 2020. 9. 6. 15:36

    (본 포스팅은 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

     

     

     

    댓글 0

Designed by Tistory.