Pinia

Pinia 적용 배경

기존 프로젝트는 Vue2 위에서 Typescript를 적용하다보니 타입 정의가 매우 복잡했는데요. (참고로 vue3+vuex4 사용 시 더 편해졌다고 해요.)

예를들어 Action 별, Mutation 별, 각 타입 지정을 위해 Enum으로 함수명을 정의해야하고 또 각 타입들을 하나의 스토어로 정의하는 과정도 필요했습니다.
또한 타입 추론 덕분에 자동완성된 내용을 활용하여 접근할 수는 있지만 코드가 점점 복잡해보였습니다. (타입 추론을 활용하기 위해 Vuex Helper 함수들은 사용하지 않았었어요.)

  • 스토어의 namespace global 모듈의 aa객체의 bb 속성에 접근
    • store.state.global.aa.bb.~
  • Enum으로 정의해둔 MutationType을 활용하여 Mutation 호출 (Commit)
    • store.commit(GlobalMutationTypes.SET_SIGNED, data);
  • Enum으로 정의해둔 ActionType을 활용하여 Action 호출 (Dispatch)
    • $store.dispatch(BizActionTypes.FETCH_ITEM_LIST);

Pinia (공식 문서)

  • vuex를 대체할 상태 관리 플러그인
  • VueConf Toronton 2021 에서 Vue의 창시자 Evan You가 상태 관리 플러그인으로 vuex가 아닌 Pinia를 추천

왜 Pinia를 사용하나요?

  • 일단 사용이 쉽습니다. (사용법은 밑에 간단하게 소개해두었습니다.)
  • mutations이 없습니다.
    • 상태 변경을 위해 Mutations을 정의하고 Commit하는 과정이 필요없습니다.
    • 그저 Vue 인스턴스의 state 값을 변경할 때 처럼 read/write 하면 됩니다.
  • Typescript를 지원하기 위해 복잡한 래핑을 하지 않아도 됩니다.
    • Pinia는 이미 TypeScript 를 통한 유형 추론을 최대한 활용할 수 있게 만들어져있습니다.
    • 굳이 타입을 별도로 지정해주지 않아도 타입 추론이 가능해요.
  • namespace modules 없음
  • devtools 도 지원합니다. (단 최신 버전을 사용해야합니다.)

사용법

스토어 정의

  • store/xxx.ts 생성
  • 기존 Vuex를 사용할 떄와 같이 State에는 상태 관리할 변수, actions 에는 서버에서 데이터를 받아와 상태 변경 등, state에 뭔가 계산이 필요한 경우 getters 를 정의합니다.
// 예시) app.ts
import { defineStore } from 'pinia';

// defineStore<모듈명, 타입>
export const useAppStore = defineStore('app', {
    state: () => ({ drawer: false, lastName: 'test' }),
    actions: {
        changeDrawer() {
            this.drawer = !this.drawer;
        },
    }, 
    getters: {
        getFullName(firstName: String) {
            return firstName + lastName;
        }
    }
});

스토어 사용 - State


  • 간단하게 useAppStore()로 store를 가져와 store 내 state에 접근하면 됩니다.

  • mutation이 없기 때문에 state 값을 v-model 에 사용할 때도 기존 vuex를 사용할 때와 같이 setter 를 따로 정의해주지 않아도 됩니다.

  • 기존 vuex 코드 (v-model 사용시)

  • Pinia 활용 코드

<input v-model="store.drawer"/>
export default {
    setup() {
        const store = useAppStore();
        return { store }
    }
}
  • 주의할 점은 구조분해 할당 시 반응성을 잃는다는 점입니다.
// 이렇게 선언할 경우 drawer는 초기값으로 계속 남아있습니다.
const { drawer } = useAppStore();
  • appStore 전체 말고 개별로 내보내고 싶은 경우에는 아래와 같은 방법이 있습니다.
// 1. 기존과 같이 computed 활용 (값의 변경도 필요한 경우 setter도 정의 필요)
return {
    count: computed(() => store.count)
}
// 2. composition API의 toRefs 활용
const { drawer } = toRefs(useAppStore());
return { drawer }

storeToRefs
Pinia 공식 문서에는 두번째 방법 (composition API의 toRefs 활용) 으로 storeToRefs를 활용하라고 나와있는데요.
현재 Vue2에서 적용이 안됩니다. 😢(#852)
Pinia의 storeToRefs 는 composition API의 toRefs와 비슷하게 동작하는데 단 Method 같은 속성은 무시하고 가져옵니다.

  • Composition API - toRefs
    • 리액티브 객체를 일반 객체로 변환하여 반환하지만, 반환되는 객체의 각 프로퍼티들이 ref로 원래의 리액티브 객체 프로퍼티로 연결됩니다.

스토어 사용 - Getters

<template>
    <div>
        fullName: {{ fullName }}
    </div>
</template>
export default {
    setup() {
        const store = useAppStore();
        return { fullName: store.getFullName }
    }
}

스토어 사용 - Action

<template>
    <div>
        <v-btn @click="store.changeDrawer">테스트</v-btn>
    </div>
</template>
export default {
    setup() {
        // #1 통째로 내보내기
        const store = useAppStore();
        return { store }
        // #2 특정 액션만 내보내기 
        // const store = useAppStore();
        // return { changeDrawer: store.changeDrawer }
    }
}

요약

  • 사용법이 매우 간단하다. (= 러닝커브가 낮다.)
  • 모듈단위로 작성하기 때문에 모듈이 많아져도 복잡해지지 않는다.
  • 신경쓰지 않아도 타입 추론을 알아서 해준다.

마치며

Pinia를 적용하고 기존 Vuex를 사용했을 때 보다 훨씬 사용이 간단해졌다고 느꼈습니다.
현재 Pinia 는 Vue의 공식 플러그인은 아니지만 추후 Pinia와 vuex 두 개의 프로젝트를 하나로 합치거나 쉽게 이동할 수 있도록 지원할 예정이라고 합니다.
새로운 프로젝트가 Vue2 + Typescript 환경일 때 상태 관리가 필요한 경우 Pinia 적용을 고려하셔도 좋을 것 같습니다.

좋은 웹페이지 즐겨찾기