Vue에서는 부모의 데이터를 자식에게 전달하기 위해서는 Props를 이용해 넘겨줍니다. 하지만 이러한 방식은 여러 컴포넌트가 중첩되어있는 경우 Props를 계속해서 연결해주게 되면 Props Drilling문제가 발생하게 됩니다.
이러한 문제를 해결하기 위해 Vue는 전역으로 상태를 관리하기 위해 Vuex를 도입하였습니다.
Vuex는 데이터를 보다 단방향적 Flux패턴을 적용하였습니다. 흔히 잘알고 있는 Redux와 원리가 같다고 볼 수 있습니다.
Vue2에서 Vue3로 업그레이드 되면서 Compostion API를 사용하여 Vue앱을 세팅하기 시작하였습니다. 또한 대규모 프로덕션 어플리케이션에서는 고려할 사항이 많아졌습니다.
-팀 협업을 위한 더욱 강력한 규칙
-타임라인, 구성요소 내 검사, 시간 이동 디버깅을 포함한 Vue DevTools와 통합
-서버 측 렌더링 지원
Pinia는 이를 구현하는 상태 관리 라이브러리로 설명되고 있습니다. 또한 기존 Vue 어플리케이션에서 사용되던 VueX보다 더 효율적이고 새로운 기능이 추가되어 최신 버전으로 구성할 경우 Pinia를 사용하는 것을 공식홈페이지에서도 추천하고 있습니다.
새로운 애플리케이션에서의 확장성을 위해 Vuex보다 Pinia를 추천하고 있습니다.(Pinia 공식문서)
다음은 Pinia공식문서의 예제입니다.
// stores/counter.js
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', {
state: () => {
return { count: 0 }
},
// could also be defined as
// state: () => ({ count: 0 })
actions: {
increment() {
this.count++
},
},
})
위의 코드와 같이 counter.js파일은 defineStore를 통해 저장소를 정의합니다. state의 경우 상태를 관리하며 action을 통해 상태를 변경 혹은 비동기 통신 등 option API의 method처럼 정의할 수 있습니다.
<script setup>
import { useCounterStore } from '@/stores/counter'
const counter = useCounterStore()
// with autocompletion ✨
counter.$patch({ count: counter.count + 1 })
// or using an action instead
counter.increment()
</script>
<template>
<!-- Access the state directly from the store -->
<div>Current Count: {{ counter.count }}</div>
</template>
그리고 정의한 저장소를 불러오고 template에 데이터를 바인딩 시킬 수 있습니다. 또한 couter저장소의 action을 통해 상태를 변경하며 &patch를 사용하여 기존 상태를 변경할 수도 있습니다.
//store/QuizDataStore.js
import { defineStore } from "pinia";
import { getQuizData } from "@/utils/api/axiosSetting";
const quizDatas = [];
export const useQuizDataStore = defineStore("quizData", {
state: () => ({ quizDatas }),
actions: {
async replaceQuizData() {
const quiz = await getQuizData();
this.quizDatas = quiz;
},
},
});
Pinia를 통해 Store를 정의하고 action을 비동기 데이터를 담는데 활용했습니다. 기존에는 빈 배열을 가지고 있다고 특정 컴포넌트가 마운트 될 때 replaceQuizData()액션을 통해 비동기로 API콜을 하여 데이터를 변경하여 렌더링을 하고자 하였습니다.
그런데 빈 값을 정의하고 Store에 변경 값을 알려주고 변경하고 다시 불러오고 서버 데이터를 이러한 방식으로 관리하는게 과연 올바른 방법인지에 대해 생각해봤습니다. 제가 기존에 리액트를 사용할 때도 클라이언트 데이터와 서버데이터를 따로 분리하여 상태를 관리하였습니다. 엄연히 다른 데이터이기 때문에 명확히 구분할 필요가 있었기 때문입니다.
추가로 따로 앱이 커지게 되면 자연스럽게 전역으로 관리해야하는 데이터가 많아지기 때문에라도 구분지을 필요가 있었습니다.
Vue에서도 React에서 서버데이터를 관리하는 React-Query처럼 Vue-Query가 있었습니다. Vue-Query의 사용방법은 React-Query를 알고 있으면 방식은 같기 때문에 따로 설명하지는 않겠습니다.
//main.js
import { VueQueryPlugin } from "@tanstack/vue-query";
import { createApp } from "vue";
import App from "./App.vue";
const app = createApp(App);
app.use(VueQueryPlugin);
app.mount("#app");
Vue-Query라이브러리 설치 이후 다음과 같이 main.js에 VueQueryPlugin을 세팅해 주고 사용하시면 됩니다. 세팅은 매우 간단합니다.
<script setup>
import QuizCard from "../../components/Organisms/QuizCard.vue";
import { useQuery } from "@tanstack/vue-query";
import { getQuizData } from "../../utils/api/axiosSetting";
import { useRoute, useRouter } from "vue-router";
const router = useRouter();
const route = useRoute();
const category = route.params.category;
/**Quiz Data Fetching */
const { data: quizDatas = [] } = useQuery(
["quizData"],
() => getQuizData(category),
{
onError: () => {
router.push("/error");
},
retry: false,
refetchOnWindowFocus: false,
}
);
</script>
<template>
<QuizCard
v-for="(quizData, index) in quizDatas"
:key="quizData.id"
:pk="index"
:answerDescription="quizData.answerDescription"
:quizType="quizData.quizType"
:answerRate="quizData.answerRate"
/>
</template>
그리고 다음과 같이 필요한 컴포넌트에서 useQuery를 사용하여 데이터를 가져왔습니다.
Reference
https://vuejs.org/guide/scaling-up/state-management.html#pinia
https://pinia.vuejs.org/core-concepts/plugins.html