상태 관리(State Management)란 애플리케이션에서 여러 컴포넌트나 모듈들이 공유하는 상태(state)를 일관되게 관리하는 방법을 말합니다. 이 상태는 보통 사용자 인터페이스(UI)의 일부나 비즈니스 로직과 관련된 데이터를 의미하며, 애플리케이션이 복잡해질수록 이를 효율적으로 관리하는 것이 중요해집니다.
Pinia는 Vue 3의 공식 상태 관리 라이브러리로, Vuex의 대안으로 설계되었습니다. Vuex와 같은 목적을 가지고 있지만, 더 가볍고 사용하기 쉬운 API를 제공합니다. Pinia는 Vue 3의 Composition API를 기반으로 설계되어, Vue 3에서 상태 관리 작업을 보다 직관적이고 효율적으로 할 수 있도록 해줍니다.
// stores/counter.js
import { defineStore } from 'pinia';
// 'useCounterStore'라는 이름의 스토어 정의
export const useCounterStore = defineStore('counter', {
state: () => ({
count: 0,
}),
actions: {
increment() {
this.count++;
},
decrement() {
this.count--;
},
},
getters: {
doubleCount: (state) => state.count * 2,
},
});
<template>
<div>
<p>{{ count }}</p>
<p>{{ doubleCount }}</p>
<button @click="increment">Increment</button>
<button @click="decrement">Decrement</button>
</div>
</template>
<script setup>
import { useCounterStore } from '@/stores/counter';
const counterStore = useCounterStore();
const { count, doubleCount } = counterStore;
const { increment, decrement } = counterStore;
</script>
사용 방법이 직관적이기 때문에 사용에 어려움은 없을 것 같아, 짚고 넘어가야 할 부분에 대해서만 정리하도록 하겠습니다.
doubleCount는 이전에 배웠던 computed와 동일하게, 캐싱도 발생합니다.
위에 정리한 내용들 외에, 아래 데이터들은 전역적으로 관리할 필요가 없기 때문에 Pinia에 저장하여 사용하는 것을 지양해야 합니다.
storeToRefs는 Pinia에서 제공하는 헬퍼 함수로, 반응형 스토어 상태와 getter를 ref로 변환하는 역할을 합니다. Vue 3의 Composition API에서 Pinia 스토어를 사용할 때, 반응성을 쉽게 관리할 수 있게 도와줍니다. storeToRefs에 대해 알아보기 전에, storeToRefs가 없다면 아래와 같이 코드를 작성해야 합니다.
import { useMyStore } from '@/stores/myStore'
import { toRef, toRefs } from 'vue'
export default {
setup() {
const store = useMyStore()
// storeToRefs 없이 상태와 getter를 ref로 변환
const count = toRef(store, 'count') // 상태를 toRef로 변환
const doubleCount = computed(() => store.doubleCount) // getter는 computed로 직접 생성
return { count, doubleCount }
}
}
useMyStore store에서 count 값을 꺼내 count라는 반응형 데이터로 변환한 것을 볼 수 있습니다. 구조 분해 할당을 사용하기 위해 toRef가 사용 되었습니다. storeToRefs를 사용하면 위 코드가 아래와 같이 개선됩니다.
import { useMyStore } from '@/stores/myStore'
import { storeToRefs } from 'pinia'
export default {
setup() {
const store = useMyStore()
// storeToRefs로 상태와 getter를 ref로 변환
const { count, doubleCount } = storeToRefs(store)
return { count, doubleCount }
}
}
mapStores도 자주 사용되는 pinia의 함수 중 하나입니다. Pinia에서 제공하는 헬퍼 함수로, Vue 컴포넌트의 computed 속성이나 methods에서 여러 스토어를 간편하게 가져와 사용하기 위해 사용됩니다. 스토어의 상태(state), getter, actions를 각각 불러오는 것보다 훨씬 간편하게 한 번에 가져올 수 있는 장점이 있습니다. mapStores또한 mapStores를 사용하는 경우와 사용하지 않는 경우 어떻게 코드가 달라지는지 확인해 보겠습니다.
mapStores를 사용하지 않은 경우
import { useCounterStore } from '@/stores/counter'
import { useUserStore } from '@/stores/user'
export default {
setup() {
const counterStore = useCounterStore()
const userStore = useUserStore()
// 스토어의 상태와 메서드를 개별적으로 사용
const count = computed(() => counterStore.count)
const userName = computed(() => userStore.name)
const incrementCount = () => {
counterStore.increment()
}
return {
count,
userName,
incrementCount,
}
}
}
mapStores를 사용한 경우
import { mapStores } from 'pinia'
import { useCounterStore, useUserStore } from '@/stores'
export default {
setup() {
// mapStores로 여러 스토어를 한 번에 가져옴
const { counterStore, userStore } = mapStores(useCounterStore, useUserStore)
const incrementCount = () => {
counterStore.increment()
}
return {
count: counterStore.count, // 직접 스토어 상태에 접근 가능
userName: userStore.name, // 직접 스토어 상태에 접근 가능
incrementCount,
}
}
}
$patch 없이 상태 업데이트
import { defineStore } from 'pinia'
export const useUserStore = defineStore('user', {
state: () => ({
name: 'John',
age: 30,
address: {
city: 'Seoul',
street: '123 Main St'
}
}),
actions: {
updateUser(name, age, city, street) {
this.name = name
this.age = age
this.address.city = city
this.address.street = street
}
}
})
$patch를 이용한 일괄 상태 업데이트
import { defineStore } from 'pinia'
export const useUserStore = defineStore('user', {
state: () => ({
name: 'John',
age: 30,
address: {
city: 'Seoul',
street: '123 Main St'
}
}),
actions: {
updateUser(data) {
this.$patch(data)
}
}
})
$reset은 Pinia 스토어의 상태를 초기 상태로 복원하는 메서드입니다. 이를 사용하면 스토어를 초기화할 수 있어, 특정 상황에서 상태를 재설정하는 데 유용합니다.
$reset 없이 상태 초기화
import { defineStore } from 'pinia'
export const useUserStore = defineStore('user', {
state: () => ({
name: 'John',
age: 30,
address: {
city: 'Seoul',
street: '123 Main St'
}
}),
actions: {
resetUser() {
this.name = 'John'
this.age = 30
this.address.city = 'Seoul'
this.address.street = '123 Main St'
}
}
})
$reset을 사용한 상태 초기화
import { defineStore } from 'pinia'
export const useUserStore = defineStore('user', {
state: () => ({
name: 'John',
age: 30,
address: {
city: 'Seoul',
street: '123 Main St'
}
}),
actions: {
resetUser() {
this.$reset()
}
}
})