Learning
레슨 7 / 9·20분

Pinia 상태 관리

Pinia란?

Pinia는 Vue.js 공식 상태 관리 라이브러리입니다. Vuex의 후속으로, TypeScript 지원이 우수하고 Composition API와 자연스럽게 통합됩니다. Store를 정의하면 어떤 컴포넌트에서든 접근할 수 있는 전역 상태를 만들 수 있습니다.

typescript
// stores/counter.ts — Setup Store 방식 (Composition API 스타일)
import { defineStore } from 'pinia';
import { ref, computed } from 'vue';

export const useCounterStore = defineStore('counter', () => {
  // state
  const count = ref(0);

  // getters (computed)
  const doubleCount = computed(() => count.value * 2);
  const isPositive = computed(() => count.value > 0);

  // actions (함수)
  function increment() {
    count.value++;
  }

  function decrement() {
    count.value--;
  }

  function reset() {
    count.value = 0;
  }

  async function incrementAsync() {
    await new Promise(r => setTimeout(r, 1000));
    count.value++;
  }

  return {
    count, doubleCount, isPositive,
    increment, decrement, reset, incrementAsync,
  };
});

컴포넌트에서 Store 사용

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

const store = useCounterStore();

// 반응성을 유지하며 구조 분해 — storeToRefs 사용
const { count, doubleCount } = storeToRefs(store);

// actions는 직접 구조 분해 가능
const { increment, decrement, reset } = store;
</script>

<template>
  <div>
    <p>카운트: {{ count }}</p>
    <p>2배: {{ doubleCount }}</p>
    <button @click="increment">+1</button>
    <button @click="decrement">-1</button>
    <button @click="reset">초기화</button>
  </div>
</template>

실전 예제: Todo Store

typescript
// stores/todo.ts
import { defineStore } from 'pinia';
import { ref, computed } from 'vue';

type Todo = {
  id: number;
  text: string;
  done: boolean;
};

export const useTodoStore = defineStore('todo', () => {
  const todos = ref<Todo[]>([]);
  let nextId = 1;

  const remaining = computed(
    () => todos.value.filter(t => !t.done).length
  );
  const completed = computed(
    () => todos.value.filter(t => t.done)
  );

  function addTodo(text: string) {
    todos.value.push({ id: nextId++, text, done: false });
  }

  function toggleTodo(id: number) {
    const todo = todos.value.find(t => t.id === id);
    if (todo) todo.done = !todo.done;
  }

  function removeTodo(id: number) {
    todos.value = todos.value.filter(t => t.id !== id);
  }

  function clearCompleted() {
    todos.value = todos.value.filter(t => !t.done);
  }

  return {
    todos, remaining, completed,
    addTodo, toggleTodo, removeTodo, clearCompleted,
  };
});
💡

storeToRefs()를 사용하지 않고 const { count } = store 처럼 직접 구조 분해하면 반응성이 사라집니다. state와 getters는 반드시 storeToRefs()로, actions는 직접 구조 분해하세요.