PromleeBlog
sitemap
aboutMe

posting thumbnail
Pinia로 간편하게 전역 상태 관리하기 - Vue 기본기 다지기 6편
Easy Global State Management with Pinia - Vue Basics Part 6

📅

🚀

들어가기 전에 🔗

이번 6편에서는 고도화된 상태 관리 기법을 알아보겠습니다.
앱이 복잡해지면 여러 컴포넌트가 동일한 데이터를 공유해야 하는 경우가 많아집니다.
예를 들어, 사용자의 로그인 정보, 장바구니 목록, 테마 설정 등은 앱의 어느 곳에서나 일관되게 유지되어야 합니다.
이런 '전역 상태'를 props로만 관리하려고 하면 매우 복잡하고 지저분한 코드가 만들어집니다.


이 문제를 해결하기 위해 Vue는 공식 상태 관리 라이브러리인
Pinia
를 제공합니다.
Pinia는 모든 컴포넌트가 공유하는 데이터를 보관하는 '중앙 저장소' 역할을 합니다.
어떤 컴포넌트든 이 저장소에 자유롭게 데이터를 저장하고 가져다 쓸 수 있어, 복잡한 데이터 흐름을 매우 단순하고 직관적으로 만들어 줍니다.

🚀

Pinia 설치 및 설정 🔗

먼저 우리 프로젝트에 Pinia를 설치합시다.
터미널에 다음 명령어를 입력합니다.
npm install pinia
설치가 끝나면, Vue 앱 전체에서 Pinia를 사용할 수 있도록 main.js 파일에서 Pinia 인스턴스를 생성하고 등록해 주어야 합니다.
src/main.js
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')
이제 우리 앱은 Pinia를 사용할 준비가 되었습니다.
일반적으로 Pinia 스토어 파일들은 src 폴더 아래에 stores라는 폴더를 만들어 그 안에서 관리합니다.

🚀

스토어 정의하기 (Store) 🔗

스토어는 특정 목적을 가진 상태(state), 계산된 값(getters), 그리고 상태를 변경하는 함수(actions)를 하나로 묶어놓은 저장소입니다.
예를 들어 '카운터 스토어', '사용자 인증 스토어'처럼 기능 단위로 스토어를 만들 수 있습니다.
src/stores/counter.js 파일을 만들고, 카운터 스토어를 정의해 보겠습니다.
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
    }
  }
})

🚀

컴포넌트에서 스토어 사용하기 🔗

이제 정의한 스토어를 컴포넌트에서 가져와 사용해 보겠습니다.
마치 Composition API의 함수를 사용하듯 간단하게 쓸 수 있습니다.
src/components/CounterComponent.vue
<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는 스토어의 stategetters 속성들을 반응성을 유지하는 ref 객체로 감싸주어 이 문제를 해결합니다.

🚀

여러 컴포넌트에서 상태 공유하기 🔗

Pinia의 진정한 힘은 여러 컴포넌트가 동일한 상태를 쉽게 공유할 수 있다는 점에서 나옵니다.
앞서 만든 CounterComponent와는 별개인 DisplayComponent에서도 카운터 값을 보여줘 보겠습니다.
src/components/DisplayComponent.vue
<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에 있는 숫자도 실시간으로 함께 바뀌는 것을 확인할 수 있습니다.
이것이 바로 중앙에서 상태를 관리하는 것의 장점입니다.

🚀

다른 상태 관리 도구와의 비교 🔗


🚀

결론 🔗

이번 시간에는 Pinia를 사용하여 애플리케이션의 전역 상태를 관리하는 방법을 배웠습니다.
Pinia를 사용하면 컴포넌트 간의 복잡한 데이터 흐름을 단순하고 명쾌하게 만들 수 있습니다.
더 이상 props를 여러 단계에 걸쳐 내릴 필요 없이, 필요한 컴포넌트에서 스토어를 직접 호출하기만 하면 됩니다.
이는 애플리케이션의 유지보수성과 확장성을 크게 향상시켜 줍니다.
다음 'Vue 기본기 다지기 7편'에서는 API 연동 및 비동기 처리에 대해 알아보겠습니다.

참고 🔗