Vue.js는 하나의 화면에는 실제 수많은 컴포넌트로 설계 되어 구성되어져 있기 때문에 컴포넌트들 간에는 부모 컴포넌트와 자식 컴포넌트의 관계가 존재합니다.
부모-자식 컴포넌트의 관계가 많아질 경우 최상위에 부모 컴포넌트의 state의 값을 변경하기 위해 하위의 있는 자식들은 계속해서 인자로 넘겨 줘야 한다는 꼬리 물기 식의 불필요한 코드가 반복적으로 생길 수 있습니다.
이러한 단점을 보안하기 위해 VUEX, PINIA와 같은 상태 관리 라이브러리가 등장했습니다. 이러한 라이브러리들은 모든 컴포넌트에 대한 중 앙 집중식 저장소 역할을 합니다.
vue.js의 상태관리 라이브러리로는 VUEX를 흔히 사용하지만, VueConf Toronto 2021 에서 Vue 의 창시자 Evan You 가 직접 등장해 추천 하는 상태 관리 플러그인을 Vuex 가 아닌 Pinia 로 공표하여 PINIA가 주목받고 있는 추세입니다.
첫번째로 pinia에서는 Vue3의 Composition API에 코드 작성 방식을 제공하고 있습니다. 따라서 기능별로 나누어 개발할 수 있기 때문에 좀 더 논리적 관점에서의 개발이 가능하게 됩니다.
// stores/list.js
import { defineStore } from "pinia";
export const useListStore = defineStore("list",{
state:()=>({list:[]}),
actions: {
addList(param){
this.list.push(param);
}
//addList: (param) => this.list.push(param);
},
getters: {
getAllList(state){
return state.list;
}
//getAllList: (state) => state.list
}
});
// stores/list.js
import { defineStore } from "pinia";
import { ref, computed } from "vue";
export const useListStore = defineStore("list", () => {
const list = ref([]);
function addList(param) {
list.value.push(param);
}
const getDataAll = computed(() => list.value);
return { list, addList, getDataAll };
});
어플리케이션의 규모가 커지면 관리해야 할 요소의 성격이 완전히 다른 경우가 종종 생깁니다. VueX는 하나의 Store만 가질 수 있기 때문에 이처럼 성격이 다른 경우에도 하나의 파일에서 관리해야 합니다.
이때 VueX에서는 namespaced modules를 이용하여 폴더를 구분한 후 성격에 따른 state와 method를 분리하여 구현할 수 있습니다. 아래와 같 이 폴더를 구분할 경우 폴더 이름이 곧 Vuex 네임스페이스가 됩니다.
namespace를 생성한후에 실제 컴포넌트에서는 다음과 같이 사용합니다.
<template>
<Home />
</template>
<script>
import { mapMutations, mapState } from "vuex";
export default {
name: "MainPage",
components: {
Home: () => import("@/components/pages/home"),
},
methods: {
//첫번째 인자에 namespace 이름을 넘겨준다.
...mapMutations("ansData", ["mutateANSData"]),
},
};
</script>
pinia는 이와 비교하여 여러 개의 store를 가질 수 있기 때문에 namespaced modules 역시 사용하지 않습니다. 이처럼 Vuex와 비교했을 때 Pinia는 간결한 구조를 가지지만 namespaced 되어 독립된 store 객체를 가진다는 컨셉은 유효합니다
import Vue from "vue";
import { useCartStore } from "./stores/cart";
import { useUserStore } from "./stores/user";
export default defineComponent({
name: "App",
setup() {
const user = useUserStore();
const cart = useCartStore();
function addItemToCart() {
cart.addItem(user.name);
}
...
VueX에서는 Store라는 저장소에 컴포넌트 간 공유할 data 속성인 state를 정의합니다. 이렇게 정의한 state 데이터를 변경할 수 있는 로직을 각각 Component에서 정의할 수도 있지만, 공통되는 로직을 매번 따로 정의할 경우, 가독성이 떨어지고 성능이 떨어질 수 있습니다. 따라서 Mutations, Actions, Getters 를 사용하여 공통으로 사용되는 로직을 한곳에서 관리합니다.
이 3가지는 공통으로 사용되는 로직을 한곳에서 관리한다는 공통점이 가지지만, 아래와 같은 차이점을 가지고 있습니다.
Mutations 의 성격상 안에 정의한 로직들이 순차적으로 일어나야 각 컴포넌트의 반영 여부를 제대로 추적할 수가 있기 때문에 동기적인 작업 만 수행할 수 있습니다. 따라서 비동기적인 로직을 수행하기 위해서는 Action 내부에 Mutations 을 commit하는 등의 작업을 추가로 진행해야 한다.
/* store.js */
import Vue from 'vue' import Vuex from 'vuex'
Vue.use(Vuex);
export default new Vuex.Store({ state: {
count: 0, },
mutations: {
increment(state) {
state.count++;
},
decrement(state) {
state.count--;
} },
actions: {
incrementAsync ({ commit }) {
setTimeout(() => {
commit('increment')
}, 1000) }
} })
pinia에서는 비동기적, 동기적 로직 모두 action에서 수행할 수 있기 때문에 mutations이 없습니다. 따라서 불필요한 코드가 줄고, 좀 더 간결한 코드를 작성할 수 있습니다.
// stores/list.js
import { defineStore } from "pinia";
export const useListStore = defineStore("list",{ state:()=>({count:0}),
actions: {
incrementAsync () {
setTimeout(() => {
state.count++;
}, 1000)
} }
});
내가 진행했던 프로젝트에서 Pinia를 도입한 이유를 다시 정리해보면 다음과 같습니다!
Composition API 활용: Pinia는 Vue 3의 Composition API를 적극적으로 활용하여 코드를 작성할 수 있게 해줍니다. 이는 기능을 논리적으로 나누어 개발할 수 있게 하며, 더 직관적이고 효율적인 코드 작성이 가능하게 합니다.
다중 Store 지원: Pinia는 여러 개의 Store를 가질 수 있어서 애플리케이션 규모가 크고 성격이 다른 부분을 더 효과적으로 관리할 수 있습니다. 이는 Vuex에서는 namespaced modules를 사용해야 하는데, Pinia에서는 별도의 네임스페이스 없이 여러 개의 독립된 Store를 사용할 수 있습니다.
간결하고 명확한 코드: Pinia는 비동기 및 동기적 로직을 모두 처리할 수 있는 actions를 제공하며, mutations가 없어서 코드가 더 간결해지고 가독성이 향상됩니다. 이는 특히 비동기 작업을 처리할 때 불필요한 코드를 줄일 수 있게 도와줍니다.
이로 인해 Pinia를 도입함으로써 프로젝트의 유지보수성과 확장성을 향상시키며, 개발 생산성을 높일 수 있었습니다.