Pinia는 Vue의 공식 상태 관리 라이브러리입니다. Vuex의 후속으로, 더 간결하고 TypeScript 친화적인 API를 제공합니다.

면접에서 "Vuex 대신 Pinia를 쓰는 이유"를 물어보면, mutations 제거, TypeScript 지원, 모듈 구조의 단순화를 핵심 포인트로 설명할 수 있어야 합니다.


설치와 설정

TYPESCRIPT
// main.ts
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'

const app = createApp(App)
app.use(createPinia())
app.mount('#app')

스토어 정의 — Setup 문법 (권장)

TYPESCRIPT
// stores/counter.ts
import { ref, computed } from 'vue'
import { defineStore } from 'pinia'

// Setup Store — Composition API와 동일한 방식
export const useCounterStore = defineStore('counter', () => {
  // state — ref()
  const count = ref(0)
  const name = ref('카운터')

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

  // actions — function
  const increment = () => {
    count.value++
  }

  const decrement = () => {
    count.value--
  }

  const reset = () => {
    count.value = 0
  }

  const incrementAsync = async () => {
    await new Promise(resolve => setTimeout(resolve, 1000))
    count.value++
  }

  return {
    count, name,
    doubleCount, displayName,
    increment, decrement, reset, incrementAsync
  }
})

스토어 정의 — Options 문법

TYPESCRIPT
// stores/user.ts
import { defineStore } from 'pinia'

interface User {
  id: number
  name: string
  email: string
}

// Options Store — Vuex와 비슷한 구조
export const useUserStore = defineStore('user', {
  state: () => ({
    currentUser: null as User | null,
    isLoggedIn: false,
    preferences: {
      theme: 'light' as 'light' | 'dark',
      language: 'ko'
    }
  }),

  getters: {
    userName: (state) => state.currentUser?.name ?? '게스트',

    // 다른 getter 사용
    greeting(): string {
      return `안녕하세요, ${this.userName}!`
    }
  },

  actions: {
    async login(email: string, password: string) {
      try {
        const response = await fetch('/api/login', {
          method: 'POST',
          body: JSON.stringify({ email, password })
        })
        this.currentUser = await response.json()
        this.isLoggedIn = true
      } catch (error) {
        console.error('로그인 실패:', error)
        throw error
      }
    },

    logout() {
      this.currentUser = null
      this.isLoggedIn = false
    },

    setTheme(theme: 'light' | 'dark') {
      this.preferences.theme = theme
    }
  }
})

컴포넌트에서 사용하기

VUE
<script setup lang="ts">
import { useCounterStore } from '@/stores/counter'
import { useUserStore } from '@/stores/user'
import { storeToRefs } from 'pinia'

const counterStore = useCounterStore()
const userStore = useUserStore()

// 직접 접근 — 반응형 유지
console.log(counterStore.count)
console.log(counterStore.doubleCount)

// storeToRefs — 구조 분해 시 반응형 유지
// 주의: actions는 storeToRefs에 포함되지 않음
const { count, doubleCount, displayName } = storeToRefs(counterStore)
const { increment, decrement, reset } = counterStore  // 액션은 직접 분해
</script>

<template>
  <div>
    <p>{{ displayName }}</p>
    <p>두 배: {{ doubleCount }}</p>
    <button @click="increment">+1</button>
    <button @click="decrement">-1</button>
    <button @click="reset">리셋</button>
  </div>

  <div>
    <p>{{ userStore.greeting }}</p>
    <button @click="userStore.setTheme('dark')">다크 모드</button>
  </div>
</template>

상태 변경 방법

TYPESCRIPT
const store = useCounterStore()

// 1. 직접 변경
store.count++

// 2. $patch — 여러 속성을 한번에 변경
store.$patch({
  count: 10,
  name: '새 카운터'
})

// 3. $patch 함수 — 복잡한 변경
store.$patch((state) => {
  state.count += 5
  state.name = `카운터 (${state.count})`
})

// 4. 액션 호출
store.increment()

// 5. $reset — 초기 상태로 리셋 (Options Store만)
// store.$reset()

Pinia vs Vuex

항목VuexPinia
mutations필수 (상태 변경은 mutations으로)없음 (직접 변경 가능)
모듈중첩 모듈 + namespace각 스토어가 독립적
TypeScript제한적네이티브 지원
Composition API제한적완벽 지원
번들 크기~10KB~1.5KB
DevTools지원지원

스토어 구독 (상태 변경 감시)

TYPESCRIPT
const store = useCounterStore()

// $subscribe — 상태 변경 감시
store.$subscribe((mutation, state) => {
  console.log('변경 타입:', mutation.type)
  console.log('새 상태:', state)

  // 로컬 스토리지에 자동 저장
  localStorage.setItem('counter', JSON.stringify(state))
})

// $onAction — 액션 호출 감시
store.$onAction(({ name, args, after, onError }) => {
  console.log(`액션 호출: ${name}`, args)

  after((result) => {
    console.log(`액션 완료: ${name}`, result)
  })

  onError((error) => {
    console.error(`액션 에러: ${name}`, error)
  })
})

면접 팁

  • Pinia에서 mutations가 사라진 이유: "상태 추적은 DevTools가 자동으로 해주므로, mutations이라는 보일러플레이트가 불필요해졌습니다"
  • storeToRefs를 써야 하는 이유를 반응형 소실 문제와 연결해서 설명하세요
  • "언제 Pinia를 쓰고, 언제 컴포넌트 로컬 상태를 쓰나요?" — 여러 컴포넌트가 공유해야 하는 상태만 스토어에 넣는 것이 원칙입니다

요약

Pinia는 Vue 3의 공식 상태 관리 라이브러리로, Setup Store(Composition API 스타일)와 Options Store 두 가지 정의 방식을 제공합니다. mutations 없이 직접 상태를 변경할 수 있고, TypeScript를 완벽 지원합니다. storeToRefs로 구조 분해 시 반응형을 유지하는 것이 핵심입니다.

댓글 로딩 중...