VueJS/VueJS

[VueJS] (Vue 3) - Vuex를 Typescript로 사용하기

썸머워즈 2022. 6. 17. 16:46
반응형

본 글은 vue-cli로 생성한 vue 프로젝트 기준으로 작성하였으며, vuex를 잘 모르겠다면 아래 글을 참고하자.

* [VueJS] Vuex 사용하기 (1) - 설치, 세팅 및 기본 사용법 (ft. Composition API)

* [VueJS] Vuex 사용하기 (2) - 기능 상세 설명 및 다양한 사용법 (ft. Composition API)


store/index.ts

보통 vue-cli를 통해 프로젝트를 생성하게 되면 index.ts가 기본적으로 생성이 되어있을 텐데,

타입 스크립트를 사용할 때는 index.ts의 경우에는 모든 타입들과, 각각의 저장소들을 한 곳에 모아주는 역할만 담당한다.

import { createStore } from "vuex";
import { CreateState, create } from "./modules/create";
import { GameState, game } from "./modules/game";

export interface RootState {
  game: GameState;
  create: CreateState;
}

export default createStore({
  modules: { game, create },
});

RootState를 통해 저장소의 서로 다른 모듈 간 state를 공유할 수 있도록 한 곳에 모아두는 역할을 하며,

사용처는 각 모듈에서 사용하기 때문에 export를 선언해둔 것이다.

 

store/modules/game.ts

import { Module } from "vuex";
import { RootState } from "..";

export interface GameState {
  answer: string;
}

export const game: Module<GameState, RootState> = {
  namespaced: true,
  state: () => ({
    answer: "",
  }),
  getters: {
    getAnswer: (state, getters, rootState): string => {
      // rootState를 통해 다른 모듈 state 추론이 가능하다.
      console.log(rootState.create.answer);
      return state.answer;
    }
  },
  mutations: {},
};

여기서 기본적으로 내장되어있는 Module<> 인터페이스를 사용하는데, 자세히 살펴보면 아래와 같다.

export interface Module<S, R> {
  namespaced?: boolean;
  state?: S | (() => S);
  getters?: GetterTree<S, R>;
  actions?: ActionTree<S, R>;
  mutations?: MutationTree<S>;
  modules?: ModuleTree<R>;
}

내장되어있는 인터페이스라 따로 선언해줄 필요는 없으며, 제네릭 요소로 두 state를 넣어둬야 한다.

그래서 첫 번째 제네릭으로는 본인의 State interface를 넣어준 것이고 두 번째 R 제네릭에는 모든 모듈 타입을 가지고 있는 RootState를 넣어준 것이다.

 

기본적으로 각 모듈마다 이런 구조를 가진다고 생각하면 된다.

 

store/modules/create.ts

import { Module } from "vuex";
import { RootState } from "..";

export interface CreateState {
  answer: string;
}

export const create: Module<CreateState, RootState> = {
  namespaced: true,
  state: () => ({
    answer: 'new Answer'
  }),
  getters: {},
  mutations: {},
};

처음 봤던 game.ts와 구조가 동일하다.

 

App.vue

<script lang="ts">
import { computed, defineComponent } from "vue";
import { useStore } from "vuex";

export default defineComponent({
  name: "App",
  setup() {
    const store = useStore();
    const answer = computed(() => store.state.create.answer);
    
    return { answer };
  },
});
</script>

사용법은 기존에 사용하던 방식과 동일하다.

 

하지만 문제가 뭐냐면 자동으로 타입 추론이 안된다는 점이다.

무슨 말이냐면 우리는 분명 저장소 store에 각 state별로 타입을 지정해 주었는데 실제로 vue 파일에서 사용하게 되면 전부 any타입으로 들어오게 된다.

 

이유는 내부적으로 제네릭 타입의 기본값이 any타입으로 되어있기 때문이다.

그렇기 때문에 실제로 저장소 값들을 사용하게 되면 타입이 다시 명시해 주는 것이 올바르다.

 

물론 Vuex에서 좀 까다롭게 설정하고 계속해서 관리를 해준다면 자동적으로 타입 추정이 되게끔 만들 수는 있으나,

그럴 바에는 차라리 Pinia를 사용하는 것을 권장한다.

 

애초에 Pinia가 좀 더 타입 스크립트와 사용하기도 좋고 Vue의 개발자 Even You 조차도 Pinia를 공식적으로 지원한다고 말했으니 이제부터 Pinia로 갈아타는 것도 나쁘지 않다고 생각된다.

 

Pinia는 기본적으로 Composition API를 지원하고 타입 추론 역시 잘 되기 때문에 Vue3 + Typescript 환경에서 개인적으로 최적이 아닐까 생각된다.


참고: https://vuex.vuejs.org/guide/typescript-support.html#typing-usestore-composition-function

 

TypeScript Support | Vuex

TypeScript Support Vuex provides its typings so you can use TypeScript to write a store definition. You don't need any special TypeScript configuration for Vuex. Please follow Vue's basic TypeScript setup to configure your project. However, if you're writi

vuex.vuejs.org

참고: https://imkh.dev/vue3-vuex-typescript/

 

Vue 3에서 Vuex와 타입스크립트 같이 사용하기

Vue 3 환경에서 Vue 전용 상태 관리 에코 시스템 Vuex와 타입스크립트로 프로젝트 구축하기

imkh.dev

반응형