본문 바로가기
카테고리 없음

Pinia — Vue의 공식 상태 관리, 군더더기 없이

by SuldenLion 2026. 6. 21.
반응형
Pinia — Vue의 공식 상태 관리, 군더더기 없이
Frontend Framework

군더더기 없는 Vue 상태 관리,
Pinia

Vuex의 복잡한 보일러플레이트를 과감히 버리고 Composition API의 철학을 온전히 담은 Vue 공식 상태 관리 라이브러리. 간결하고, 타입 안전하며, 직관적이다.

프론트엔드 개발 · 프레임워크 & 라이브러리 · Pinia

Vue 2 시절 공식 상태 관리 도구는 Vuex였다. 작동은 했지만, mutations와 actions의 이중 구조, 장황한 보일러플레이트, 미흡한 TypeScript 지원이 늘 불편했다. Pinia는 그 모든 불편함을 해소하며 Vue 공식 상태 관리 라이브러리 자리를 넘겨받았다.

Vuex vs Pinia

항목 Vuex 4 Pinia
Mutations 필요 여부 필수 불필요, actions만 사용
TypeScript 지원 추가 설정 필요 완전 지원 (타입 추론 자동)
DevTools 지원 지원 (time-travel 포함)
모듈 구조 중첩 모듈, 네임스페이스 스토어 파일 분리, 자동 코드 분할
번들 크기 ~10KB ~1.5KB (gzip)

스토어의 세 가지 구성 요소

Pinia 스토어는 세 가지 개념으로 구성된다.

state 스토어의 데이터. 반응성 있는 상태 변수들의 집합. ref() 또는 reactive()로 정의한다.
getters state에서 파생되는 계산 값. Vue의 computed와 동일. 캐싱이 자동으로 적용된다.
actions state를 변경하는 메서드. 동기와 비동기 모두 지원. Vuex처럼 mutations와 actions를 구분하지 않는다.

스토어 정의하기

Pinia는 두 가지 방식으로 스토어를 정의할 수 있다. Options 방식과 Setup 방식이다.

Options 방식 (Vuex에 익숙한 경우)

// stores/counter.js
import { defineStore } from 'pinia'

export const useCounterStore = defineStore('counter', {
  state: () => ({
    count: 0,
    name: '카운터',
  }),
  getters: {
    doubleCount: (state) => state.count * 2,
    label() {
      return `${this.name}: ${this.count}`
    }
  },
  actions: {
    increment() {
      this.count++
    },
    async fetchAndSet() {
      const data = await fetchCount()
      this.count = data.value
    }
  }
})

Setup 방식 (Composition API 스타일, 권장)

import { defineStore } from 'pinia'
import { ref, computed } from 'vue'

export const useCounterStore = defineStore('counter', () => {
  // state → ref / reactive
  const count = ref(0)
  const name = ref('카운터')

  // getters → computed
  const doubleCount = computed(() => count.value * 2)
  const label = computed(() => `${name.value}: ${count.value}`)

  // actions → 일반 함수
  function increment() { count.value++ }
  async function fetchAndSet() {
    const data = await fetchCount()
    count.value = data.value
  }

  return { count, name, doubleCount, label, increment, fetchAndSet }
})

Setup 방식 권장: Composition API에 익숙하다면 Setup 방식이 훨씬 자연스럽다. Composable을 스토어 안에서 직접 사용할 수도 있고, 외부 Composable과의 통합도 쉽다.

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

<script setup>
import { useCounterStore } from '@/stores/counter'
import { storeToRefs } from 'pinia'

const store = useCounterStore()

// storeToRefs: 반응성 유지하며 구조 분해
const { count, doubleCount, label } = storeToRefs(store)

// actions는 직접 구조 분해 가능 (반응성 불필요)
const { increment, fetchAndSet } = store
</script>

<template>
  <p>{{ label }}</p>
  <p>두 배: {{ doubleCount }}</p>
  <button @click="increment">+1</button>
</template>

storeToRefs 주의: 스토어를 구조 분해할 때 반응성이 사라지는 문제를 막으려면 storeToRefs()를 사용해야 한다. actions는 함수이므로 storeToRefs 없이 바로 꺼내도 된다.

스토어 간 통신

Pinia에서는 스토어끼리 서로를 자유롭게 참조할 수 있다. Vuex처럼 루트 스토어를 거치지 않아도 된다.

// stores/order.js — auth 스토어를 참조
import { defineStore } from 'pinia'
import { useAuthStore } from './auth'

export const useOrderStore = defineStore('order', () => {
  async function placeOrder(items) {
    const authStore = useAuthStore()
    if (!authStore.isLoggedIn) {
      throw new Error('로그인이 필요합니다')
    }
    await submitOrder(authStore.userId, items)
  }
  return { placeOrder }
})

플러그인과 확장

Pinia는 플러그인 시스템을 통해 기능을 확장할 수 있다. 로컬스토리지 동기화, 로깅, 에러 트래킹 등을 스토어 전체에 일괄 적용할 수 있다.

pinia-plugin-persistedstate

스토어 상태를 localStorage나 sessionStorage에 자동 저장/복원. 새로고침 후에도 상태 유지.

Vue DevTools 통합

각 스토어의 상태 변화를 타임라인으로 추적. 액션 단위의 time-travel 디버깅 지원.

Pinia는 작은 크기(~1.5KB), 직관적인 API, 완전한 TypeScript 지원, Composition API와의 자연스러운 통합으로 Vue 3 프로젝트에서 사실상 표준 상태 관리 도구가 되었다. Vuex를 쓰고 있다면 마이그레이션을 적극 고려할 만하다.

반응형

댓글