์นดํ…Œ๊ณ ๋ฆฌ ์—†์Œ

[KB IT’s your life 7๊ธฐ] Vue3 Pinia ์ƒํƒœ๊ด€๋ฆฌ ์™„๋ฒฝ ์ •๋ฆฌ

yeye 2026. 4. 11. 13:21

์•ˆ๋…•ํ•˜์„ธ์š”! KB ITโ€™s your life 7๊ธฐ ๊ธฐ์ž๋‹จ์ž…๋‹ˆ๋‹ค
์ €๋Š” ์š”์ฆ˜ ์Šค์ผˆ๋ ˆํ†ค ํ”„๋กœ์ ํŠธ ํ•œ์ฐฝ ์ง„ํ–‰ ์ค‘์ธ๋ฐ์š”!
Pinia๋ฅผ ์จ์•ผ ํ•˜๋Š” ์ƒํ™ฉ์ด ์™”๋Š”๋ฐ ๋ง‰์ƒ ์“ฐ๋ ค๋‹ˆ๊นŒ ์ˆ˜์—… ๋•Œ ๋ฐฐ์› ๋˜ ๋‚ด์šฉ์ด ์ •๋ฆฌ๊ฐ€ ์•ˆ ๋œ ๋А๋‚Œ์ด๋”๋ผ๊ณ ์š”...
๊ทธ๋ž˜์„œ ์ด๋ฒˆ ๊ธฐํšŒ์— ์ œ๋Œ€๋กœ ๋ณต์Šต ๊ฒธ ์ •๋ฆฌํ•ด ๋ดค์–ด์š”!
์ €์ฒ˜๋Ÿผ Pinia ๊ฐœ๋…์ด ์•„์ง ์–ด๋ ต๋‹ค! ํ•˜๋Š” ๋ถ„๋“ค๊ป˜๋„
๋„์›€์ด ๋์œผ๋ฉด ์ข‹๊ฒ ์Šต๋‹ˆ๋‹ค ใ…Žใ…Ž

Pinia ์ „์— ์•Œ์•„์•ผ ํ•  ๊ฒƒ

Pinia๋ฅผ ์™œ ์จ์•ผ ํ•˜๋Š”์ง€ ์ดํ•ดํ•˜๋ ค๋ฉด
๋จผ์ € ๊ธฐ์กด ๋ฐฉ์‹์ธ Provide/Inject๊ฐ€ ๋ญ๊ฐ€ ๋ฌธ์ œ์ธ์ง€๋ถ€ํ„ฐ
์•Œ์•„์•ผ ํ•ด์š”!
Provide/Inject๋Š” ๋ถ€๋ชจ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋ฐ์ดํ„ฐ๋ฅผ ์ œ๊ณต(Provide)ํ•˜๊ณ 
์ž์‹ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋ฐ›์•„์„œ(Inject) ์“ฐ๋Š” ๋ฐฉ์‹์ด์—์š”
๊ทผ๋ฐ ํ”„๋กœ์ ํŠธ๊ฐ€ ์ปค์ง€๋‹ˆ๊นŒ ์ด๋Ÿฐ ์ƒํ™ฉ์ด ์ƒ๊ฒจ์š”

๋ฌธ์ œ 1. ํ˜•์ œ ์ปดํฌ๋„ŒํŠธ๋ผ๋ฆฌ ๋ฐ์ดํ„ฐ ๊ณต์œ ๊ฐ€ ์•ˆ ๋ผ์š”

App (Provide)
โ”œโ”€โ”€ C1 (Inject ๊ฐ€๋Šฅ )
โ””โ”€โ”€ C2 (Inject ๊ฐ€๋Šฅ )
    โ””โ”€โ”€ C2-1 (C1 ๋ฐ์ดํ„ฐ ๋ชป ๋ฐ›์Œ )

Provide/Inject๋Š” ๊ฐ™์€ ํŠธ๋ฆฌ ์•ˆ์—์„œ๋งŒ ๋ฐ์ดํ„ฐ๋ฅผ ๊ณต์œ ํ•  ์ˆ˜ ์žˆ์–ด์š”
C1์ด๋ž‘ C2-1์ฒ˜๋Ÿผ ์„œ๋กœ ๋‹ค๋ฅธ ๊ฐ€์ง€์— ์žˆ์œผ๋ฉด ๊ณต์œ ๊ฐ€ ์•ˆ ๋˜๊ฑฐ๋“ ์š”


์ „์ฒด ์•ฑ์—์„œ ๋‹ค ๊ณต์œ ํ•˜๋ ค๋ฉด ๋ฌด์กฐ๊ฑด ์ตœ์ƒ๋‹จ App์—์„œ Provide ํ•ด์•ผ ํ•˜๋Š”๋ฐ ๊ทธ๋Ÿฌ๋ฉด ์ง€๊ธˆ ๋‹น์žฅ ์•ˆ ์“ฐ๋Š” ๋ฐ์ดํ„ฐ๊นŒ์ง€ ํ•ญ์ƒ ๋ฉ”๋ชจ๋ฆฌ์— ์˜ฌ๋ผ๊ฐ€ ์žˆ์–ด์•ผ ํ•ด์„œ ๋น„ํšจ์œจ์ ์ด์—์š”.

2. ๋ฌธ์ œ ์ƒํƒœ ๋ฐ์ดํ„ฐ ๋ณดํ˜ธ๊ฐ€ ๊ฐœ๋ฐœ์ž ์ฑ…์ž„์ด์—์š”

์ „์—ญ์œผ๋กœ ๊ณต์œ ๋˜๋Š” ๋ฐ์ดํ„ฐ๋Š” ์•„๋ฌด๋‚˜ ์ˆ˜์ • ๋ชป ํ•˜๊ฒŒ
์ฝ๊ธฐ ์ „์šฉ์œผ๋กœ ๋…ธ์ถœํ•ด์•ผ ํ•ด์š”.
๊ทผ๋ฐ Provide/Inject๋Š” ์ด๊ฑธ ์‹œ์Šคํ…œ์ด ๋ง‰์•„์ฃผ๋Š” ๊ฒŒ ์•„๋‹ˆ๋ผ ๊ฐœ๋ฐœ์ž๊ฐ€ ์ง์ ‘ ์‹ ๊ฒฝ ์จ์•ผ ํ•ฉ๋‹ˆ๋‹ค.

๊ทธ๋ž˜์„œ computed๋กœ ๊ฐ์‹ธ์„œ ์ฝ๊ธฐ ์ „์šฉ์œผ๋กœ ๋…ธ์ถœํ•˜๋Š”๋ฐ์š”

const readOnlyList = computed(() => state.todoList)
provide('todoList', readOnlyList) // ์ฝ๊ธฐ ์ „์šฉ์œผ๋กœ ์ „๋‹ฌ
provide('addTodo', addTodo)       // action ํ•จ์ˆ˜๋Š” ๊ทธ๋Œ€๋กœ OK

์ด๊ฒŒ ์‹œ์Šคํ…œ์ด ๊ฐ•์ œํ•ด ์ฃผ๋Š” ๊ฒŒ ์•„๋‹ˆ๋ผ์„œ
์‹ค์ˆ˜ํ•˜๊ธฐ ๋”ฑ ์ข‹์•„์š” ใ…œใ…œ

๊ทธ๋ž˜์„œ Pinia๊ฐ€ ๋ญ”๋ฐ์š”?

Pinia(ํ”ผ๋‹ˆ์•„)๋Š” Vue3์˜ ๊ณต์‹ ์ƒํƒœ๊ด€๋ฆฌ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์˜ˆ์š”
ํ•œ๋งˆ๋””๋กœ ์—ฌ๋Ÿฌ ์ปดํฌ๋„ŒํŠธ์—์„œ ๊ณตํ†ต์œผ๋กœ ์“ฐ๋Š” ๋ฐ์ดํ„ฐ๋ฅผ
ํ•œ ๊ณณ์—์„œ ๊ด€๋ฆฌํ•˜๋Š” ์ „์—ญ ์ €์žฅ์†Œ๋ผ๊ณ  ๋ณด๋ฉด ๋ผ์š”!


Provide/Inject๋ž‘ ๋น„๊ตํ•˜๋ฉด ์ด๋Ÿฐ ์ฐจ์ด๊ฐ€ ์žˆ์–ด์š”:

์ปดํฌ๋„ŒํŠธ ๊ฒฐํ•ฉ๊ฐ•ํ•˜๊ฒŒ ๊ฒฐํ•ฉ์™„์ „ ๋…๋ฆฝ
๊ณต์œ  ๋ฒ”์œ„ํ•˜์œ„ ์ปดํฌ๋„ŒํŠธ๋งŒ์•ฑ ์ „์ฒด
๋ชจ๋“ˆํ™”์–ด๋ ค์›€์Šคํ† ์–ด ๋‹จ์œ„๋กœ ์‰ฝ๊ฒŒ
๋ฉ”๋ชจ๋ฆฌ ํšจ์œจํ•ญ์ƒ ๋ฉ”๋ชจ๋ฆฌ ์ƒ์ฃผํ•„์š”ํ•  ๋•Œ ์ƒ์„ฑ

Pinia Store ๋งŒ๋“ค๊ธฐ (defineStore)

์Šคํ† ์–ด๋Š” src/stores ํด๋” ์•ˆ์— ๋งŒ๋“œ๋Š” ๊ฒŒ ๊ด€๋ก€์˜ˆ์š”.

// stores/todoStore.js
import { defineStore } from 'pinia'
import { reactive, computed } from 'vue'

export const useTodoStore = defineStore('todo', () => {

  // State โ€” ๋ฐ˜์‘ํ˜• ์ƒํƒœ ๋ฐ์ดํ„ฐ
  const state = reactive({
    todoList: [],
    isLoading: false,
  })

  // Getter โ€” ์ฝ๊ธฐ ์ „์šฉ ๋…ธ์ถœ
  const todoList = computed(() => state.todoList)
  const isLoading = computed(() => state.isLoading)
  const doneCount = computed(() =>
    state.todoList.filter(todo => todo.done). length
  )

  // Action โ€” ์ƒํƒœ๋ฅผ ์ˆ˜์ •ํ•˜๋Š” ํ•จ์ˆ˜
  function addTodo(todo) {
    state.todoList.push(todo)
  }
  function deleteTodo(id) {
    state.todoList = state.todoList.filter(t => t.id!== id)
  }
  function toggleDone(id) {
    const todo = state.todoList.find(t => t.id === id)
    if (todo) todo.done =! todo.done
  }

  // ์™ธ๋ถ€์— ๋…ธ์ถœํ•  ํ•ญ๋ชฉ ๋ฐ˜ํ™˜
  return {
    todoList, isLoading, doneCount,
    addTodo, deleteTodo, toggleDone
  }
})

์—ฌ๊ธฐ์„œ ์ œ์ผ ์ค‘์š”ํ•œ ํฌ์ธํŠธ!
state์ž์ฒด๋Š” ์ ˆ๋Œ€ ๋ฐ˜ํ™˜ํ•˜์ง€ ์•Š์•„์š”.
computed๋กœ ๊ฐ์‹ผ ์ฝ๊ธฐ ์ „์šฉ ๋ฒ„์ „๋งŒ ๋…ธ์ถœํ•ด์„œ
์™ธ๋ถ€์—์„œ ์ง์ ‘ ์ˆ˜์ •ํ•˜๋Š” ๊ฑธ ๋ง‰์•„์•ผ ํ•ฉ๋‹ˆ๋‹ค.


์ปดํฌ๋„ŒํŠธ์—์„œ ์Šคํ† ์–ด ์‚ฌ์šฉํ•˜๊ธฐ

<script setup>
import { computed } from 'vue'
import { useTodoStore } from '@/stores/todoStore'

const store = useTodoStore()

// Action ํ•จ์ˆ˜๋Š” ๋ถ„ํ•ดํ• ๋‹น OK
const { addTodo, deleteTodo, toggleDone } = store

// ์ฐธ์กฐ ํƒ€์ž…(๋ฐฐ์—ด)๋„ ๋ถ„ํ•ดํ• ๋‹น OK โ€” ๋ฐ˜์‘์„ฑ ์œ ์ง€๋จ
const { todoList } = store

// ์›์‹œ ํƒ€์ž…(์ˆซ์ž)์€ ๋ฐ˜๋“œ์‹œ computed๋กœ!
const doneCount = computed(() => store.doneCount)
</script>

<template>
  <div>
    <p> ์™„๋ฃŒ๋œ ํ•  ์ผ: {{ doneCount }}</p>
    <ul>
      <li v-for="todo in todoList" :key="todo.id">
        {{ todo.title }}
        <button @click="toggleDone(todo.id)">์™„๋ฃŒ</button>
        <button @click="deleteTodo(todo.id)">์‚ญ์ œ</button>
      </li>
    </ul>
  </div>
</template>

๊ฐ€์žฅ ๋งŽ์ด ํ•˜๋Š” ์‹ค์ˆ˜ โ€” ์›์‹œ ํƒ€์ž… ๋ถ„ํ•ดํ• ๋‹น

์ด๊ฑฐ ์ง„์งœ ์ค‘์š”ํ•ด์„œ ๋”ฐ๋กœ ์ •๋ฆฌํ•ด์š”.
์Šคํ† ์–ด์—์„œ ๋ฐ์ดํ„ฐ ๊บผ๋‚ผ ๋•Œ ๊ตฌ์กฐ ๋ถ„ํ•ด ํ• ๋‹น ํŽธํ•˜๊ฒŒ ์“ฐ๊ณ  ์‹ถ์ž–์•„์š”.
๊ทผ๋ฐ ์ˆซ์ž, ๋ฌธ์ž์—ด, ๋ถˆ๋ฆฌ์–ธ ๊ฐ™์€ ์›์‹œ ํƒ€์ž…์„ ๋ถ„ํ•ดํ• ๋‹นํ•˜๋ฉด ๋ฐ˜์‘์„ฑ์ด ๊นจ์ ธ๋ฒ„๋ ค์š”!

// ์›์‹œ ํƒ€์ž… ๋ถ„ํ•ดํ• ๋‹น โ€” ๋ฐ˜์‘์„ฑ ๊นจ์ง
const { doneCount } = store
// doneCount๊ฐ€ ๋ฐ”๋€Œ์–ด๋„ ํ™”๋ฉด์ด ์•ˆ ๋ฐ”๋€œ!

// ์˜ฌ๋ฐ”๋ฅธ ๋ฐฉ๋ฒ•
const doneCount = computed(() => store.doneCount)

์™œ ์ด๋Ÿฐ ์ผ์ด ์ƒ๊ธฐ๋ƒ๋ฉด
์›์‹œ ํƒ€์ž…์€ ๋ถ„ํ•ดํ• ๋‹น ์‹œ ๊ฐ’์ด ๋ณต์‚ฌ๋ผ์š”.


๋ณต์‚ฌ๋œ ๊ฐ’์€ ์›๋ณธ ์Šคํ† ์–ด๋ž‘ ์—ฐ๊ฒฐ์ด ๋Š๊ฒจ์„œ ๋ฐ˜์‘์„ฑ์ด ์‚ฌ๋ผ์ง€๊ฑฐ๋“ ์š”.
๋ฐ˜๋ฉด ๋ฐฐ์—ดยท๊ฐ์ฒด ๊ฐ™์€ ์ฐธ์กฐ ํƒ€์ž…์€ ์ฐธ์กฐ๊ฐ€ ๋ณต์‚ฌ๋˜๊ธฐ ๋•Œ๋ฌธ์—
์›๋ณธ์ด๋ž‘ ์—ฐ๊ฒฐ์ด ์œ ์ง€๋ผ์„œ ๋ฐ˜์‘์„ฑ์ด ์‚ด์•„์žˆ์–ด์š”.

ํƒ€์ž… ๋ถ„ํ•ดํ• ๋‹น ๊ถŒ์žฅ ๋ฐฉ๋ฒ•
number, string, boolean ๋ฐ˜์‘์„ฑ ๊นจ์ง computed(() => store.value)
Array, Object ๋ฐ˜์‘์„ฑ ์œ ์ง€ ๋ถ„ํ•ดํ• ๋‹น ๊ฐ€๋Šฅ
Function (Action) ๋ฌธ์ œ์—†์Œ ๋ถ„ํ•ดํ• ๋‹น ๊ฐ€๋Šฅ

์ƒˆ๋กœ๊ณ ์นจ ํ•˜๋ฉด ๋ฐ์ดํ„ฐ๊ฐ€ ์‚ฌ๋ผ์ ธ์š” โ€” Local Storage ์—ฐ๋™

Pinia ์ƒํƒœ๋Š” ๋ฉ”๋ชจ๋ฆฌ์—๋งŒ ์žˆ์–ด์„œ
์ƒˆ๋กœ๊ณ ์นจ ํ•˜๋ฉด ์ดˆ๊ธฐํ™”๋ผ์š”.
์„œ๋ฒ„ ์—†์ด ๋ฐ์ดํ„ฐ๋ฅผ ์œ ์ง€ํ•˜๋ ค๋ฉด
Local Storage์— ์ €์žฅํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค!

function addTodo(todo) {
  state.todoList.push(todo)
  syncStorage() // ์ƒํƒœ ๋ณ€๊ฒฝํ•  ๋•Œ๋งˆ๋‹ค ์ €์žฅ
}

function deleteTodo(id) {
  state.todoList = state.todoList.filter(t => t.id !== id)
  syncStorage()
}

// ๋กœ์ปฌ ์Šคํ† ๋ฆฌ์ง€์— ์ €์žฅ (๋ฌธ์ž์—ด๋กœ ์ง๋ ฌํ™”)
function syncStorage() {
  localStorage.setItem('todos', JSON.stringify(state.todoList))
}

// ์•ฑ ์‹œ์ž‘ํ•  ๋•Œ ๋กœ์ปฌ ์Šคํ† ๋ฆฌ์ง€์—์„œ ๋ณต์›
function loadFromStorage() {
  const saved = localStorage.getItem('todos')
  if (saved) state.todoList = JSON.parse(saved)
}

Local Storage๋Š” ๋ฌธ์ž์—ด๋งŒ ์ €์žฅํ•  ์ˆ˜ ์žˆ์–ด์„œ
JSON.stringify()๋กœ ์ €์žฅํ•˜๊ณ  JSON.parse()๋กœ ๊บผ๋‚ด์•ผ ํ•ด์š”!


์•„ํ‚คํ…์ฒ˜ ๊ณ ๋ฏผ โ€” ๋‹จ์ผ ์ฑ…์ž„ ์›์น™ (SRP)

์Šคํ† ์–ด ์•ˆ์— ์„œ๋ฒ„ ํ†ต์‹  ์ฝ”๋“œ๊นŒ์ง€ ๋„ฃ์œผ๋ฉด ํŽธํ•˜๊ธด ํ•œ๋ฐ,
์ด๋Ÿฌ๋ฉด Pinia ์ฑ…์ž„์ด ๋‘ ๊ฐœ๊ฐ€ ๋ผ๋ฒ„๋ ค์š”.

  • ์ƒํƒœ ๊ด€๋ฆฌ (์›๋ž˜ Pinia ์—ญํ• )
  • ์„œ๋ฒ„ ํ†ต์‹  (์ถ”๊ฐ€๋œ ์—ญํ• )

๋‹จ์ผ ์ฑ…์ž„ ์›์น™(SRP)
โ€œํ•˜๋‚˜์˜ ๋ชจ๋“ˆ์€ ํ•˜๋‚˜์˜ ์ด์œ ๋กœ๋งŒ ์ˆ˜์ •๋˜์–ด์•ผ ํ•œ๋‹ค.โ€

API URL์ด ๋ฐ”๋€Œ์–ด๋„, ์ƒํƒœ ๊ตฌ์กฐ๊ฐ€ ๋ฐ”๋€Œ์–ด๋„
๋‘˜ ๋‹ค ์Šคํ† ์–ด๋ฅผ ์ˆ˜์ •ํ•ด์•ผ ํ•˜๋Š” ์ƒํ™ฉ์ด ์ƒ๊ธฐ๊ฑฐ๋“ ์š”.
๊ทธ๋ž˜์„œ ํ†ต์‹  ์ฝ”๋“œ๋Š” ๋ณ„๋„ ํŒŒ์ผ๋กœ ๋ถ„๋ฆฌํ•˜๋Š” ๊ฒŒ ์ข‹์•„์š”!

// ์Šคํ† ์–ด๊ฐ€ ํ†ต์‹ ๊นŒ์ง€ ๋‹ด๋‹น โ€” SRP ์œ„๋ฐ˜
async function fetchTodos() {
  const res = await fetch('https://api.example.com/todos')
  state.todoList = await res.json()
}

// API๋Š” ๋ณ„๋„ ํŒŒ์ผ๋กœ ๋ถ„๋ฆฌ
// api/todoApi.js โ€” ํ†ต์‹ ๋งŒ ๋‹ด๋‹น
export const getTodos = () =>
  fetch('https://api.example.com/todos').then(r => r.json())

// stores/todoStore.js โ€” ์ƒํƒœ ๊ด€๋ฆฌ๋งŒ ๋‹ด๋‹น
import { getTodos } from '@/api/todoApi'

async function fetchTodos() {
  state.isLoading = true
  state.todoList = await getTodos() // ํ˜ธ์ถœ๋งŒ!
  state.isLoading = false
}

์ด๋ ‡๊ฒŒ ํ•˜๋ฉด

  • API ์ฃผ์†Œ ๋ฐ”๋€Œ๋ฉด โ†’ todoApi.js๋งŒ ์ˆ˜์ •
  • ์ƒํƒœ ๊ตฌ์กฐ ๋ฐ”๋€Œ๋ฉด โ†’ todoStore.js๋งŒ ์ˆ˜์ •

์ˆ˜์ • ์ด์œ ๊ฐ€ ๋”ฑ ํ•˜๋‚˜์”ฉ์ด ๋˜๋Š” ๊ฑฐ์˜ˆ์š” ๐Ÿ˜Ž


๋งˆ๋ฌด๋ฆฌ

๋ณต์Šตํ•˜๋ฉด์„œ ์ •๋ฆฌํ•˜๋‹ค ๋ณด๋‹ˆ๊นŒ
์ˆ˜์—… ๋•Œ ํ˜๋ ค๋“ค์—ˆ๋˜ ๋ถ€๋ถ„๋“ค์ด ์ด์ œ์•ผ ์ œ๋Œ€๋กœ ์ดํ•ด๋์–ด์š”.
์Šค์ผˆ๋ ˆํ†ค ํ”„๋กœ์ ํŠธ์—์„œ Pinia ์‹ค์ œ๋กœ ์จ๋ณด๋ฉด์„œ
๋” ์ตํ˜€๋ด์•ผ๊ฒ ์Šต๋‹ˆ๋‹ค ๐Ÿ’ช
๊ฐ™์€ ๋‚ด์šฉ์œผ๋กœ ํ—ท๊ฐˆ๋ฆฌ์…จ๋˜ ๋ถ„๋“ค๊ป˜ ๋„์›€์ด ๋์œผ๋ฉด ์ข‹๊ฒ ์–ด์š”!
๋‹ค์Œ ํฌ์ŠคํŒ…์—์„œ ๋ดฌ์š” ๐Ÿ˜Š