props
로만 관리하려고 하면 매우 복잡하고 지저분한 코드가 만들어집니다.npm install pinia
main.js
파일에서 Pinia 인스턴스를 생성하고 등록해 주어야 합니다.import { createApp } from 'vue'
import { createPinia } from 'pinia' // Pinia에서 createPinia를 가져옵니다.
import App from './App.vue'
import router from './router'
const app = createApp(App)
app.use(createPinia()) // Pinia 인스턴스를 생성하여 앱에 등록합니다.
app.use(router)
app.mount('#app')
src
폴더 아래에 stores
라는 폴더를 만들어 그 안에서 관리합니다.src/stores/counter.js
파일을 만들고, 카운터 스토어를 정의해 보겠습니다.import { defineStore } from 'pinia'
// `defineStore`를 사용하여 스토어를 정의합니다.
// 첫 번째 인자는 스토어의 고유 ID(문자열)입니다.
// 두 번째 인자는 state, getters, actions를 포함하는 객체입니다.
export const useCounterStore = defineStore('counter', {
// 1. state: 스토어의 상태(데이터)를 정의합니다. 반드시 화살표 함수로 반환해야 합니다.
state: () => ({
count: 0,
userName: 'promleeblog'
}),
// 2. getters: state를 기반으로 하는 계산된 값입니다. Vue의 computed와 같습니다.
getters: {
doubleCount: (state) => state.count * 2,
doubleCountPlusOne: (state) => {
// this를 통해 다른 getter에 접근할 수도 있습니다.
return this.doubleCount + 1
}
},
// 3. actions: state를 변경하는 함수(메소드)입니다. Vue의 methods와 같습니다.
actions: {
increment() {
// state의 속성에 접근할 때는 'this'를 사용합니다.
this.count++
},
reset() {
this.count = 0
}
}
})
<script setup>
import { useCounterStore } from '@/stores/counter'
import { storeToRefs } from 'pinia'
// 정의한 스토어를 호출하여 인스턴스를 만듭니다.
const counterStore = useCounterStore()
// 중요: 스토어의 상태를 구조 분해 할당하면 반응성을 잃게 됩니다.
// 반응성을 유지하면서 가져오려면 `storeToRefs`를 사용해야 합니다.
const { count, doubleCount } = storeToRefs(counterStore)
// actions는 구조 분해 할당해도 괜찮습니다.
const { increment, reset } = counterStore
</script>
<template>
<div>
<h2>카운터</h2>
<p>현재 값: {{ count }}</p>
<p>두 배 값: {{ doubleCount }}</p>
<button @click="increment">증가</button>
<button @click="counterStore.reset()">초기화</button>
</div>
</template>
storeToRefs
는 매우 중요한 유틸리티입니다.
만약 const { count } = counterStore
처럼 직접 구조 분해 할당을 하면, count
는 반응성을 잃어버린 일반 숫자가 되어버립니다.
storeToRefs
는 스토어의 state
와 getters
속성들을 반응성을 유지하는 ref
객체로 감싸주어 이 문제를 해결합니다.CounterComponent
와는 별개인 DisplayComponent
에서도 카운터 값을 보여줘 보겠습니다.<script setup>
import { useCounterStore } from '@/stores/counter'
import { storeToRefs } from 'pinia'
// 동일한 스토어를 호출합니다. Pinia가 알아서 같은 인스턴스를 반환합니다.
const counterStore = useCounterStore()
const { count } = storeToRefs(counterStore)
</script>
<template>
<div>
<h3>다른 컴포넌트에서 본 카운트 값: {{ count }}</h3>
</div>
</template>
App.vue
에 배치해 봅시다.
CounterComponent
의 버튼을 누르면 DisplayComponent
에 있는 숫자도 실시간으로 함께 바뀌는 것을 확인할 수 있습니다.
이것이 바로 중앙에서 상태를 관리하는 것의 장점입니다.state
, getters
, actions
를 하나의 스토어에 묶어 관리하는 구조적인 접근 방식을 취합니다.
가장 큰 차이는 Pinia는 Vue의 다음 'Vue 기본기 다지기 7편'에서는 API 연동 및 비동기 처리에 대해 알아보겠습니다.