새발블로그

[Vue.js] Pinia로 Vue 상태 관리하기 본문

Client/Vue.js

[Vue.js] Pinia로 Vue 상태 관리하기

EUG 2025. 5. 4. 17:15

Vue 3에서는 상태 관리와 관련된 문제 해결을 위해 Pinia를 사용한다.

여러 컴포넌트에서 공통 데이터를 관리하고 공유할 수 있다.

1. Pinia란?

Pinia를 사용하면 여러 컴포넌트 간에 공통 데이터를 store로 관리하고 쉽게 공유할 수 있다. Pinia는 React와 비슷한 접근법으로 컴포넌트 단위로 데이터를 관리하며, 모듈화된 상태 관리를 지원한다.

Pinia를 사용하면 store를 정의하고, 그 안에서 state, actions, getters 등을 관리할 수 있습니다. 이로써 앱의 데이터 흐름을 한 곳에서 관리하고 쉽게 공유할 수 있다.

 

2. Store 생성 및 사용 방법

Pinia를 사용하려면 먼저 store를 생성해야하고,  Pinia store는 defineStore 함수를 사용하여 정의한다. 

store 생성 예시

src/stores/counter.js

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

export const useCount1Store = defineStore('count1', () => {
  // state: 데이터 상태 관리
  const state = reactive({ count: 0 })

  // actions: 비즈니스 로직 (데이터를 수정하는 함수들)
  const increment = (num) => {
    state.count += num
  }

  // ref와 computed 사용 예시
  const count = ref(0)

  const plus = () => {
    count.value++
  }

  // computed: 파생 데이터 (캐싱)
  const test = computed(() => {
    console.log('test computed 실행')
    return count.value * 2
  })

  return { state, increment, count, plus, test }
})

 

  • state: 앱 전체 또는 특정 기능의 데이터를 관리. 
  • actions: state를 수정하는 함수들입니다.
  • computed: state에 의존하는 파생 데이터를 계산하고 캐싱

3. 주요 개념

state 앱 전체 또는 특정 기능의 데이터 상태를 관리
action 데이터 수정 또는 비즈니스 로직을 수행하는 함수
getter (computed) 파생된 데이터를 반환하는 함수입니다. 캐싱되어 성능 최적화에 도움

 

4. computed 캐싱 동작 흐름

Pinia에서 computed는 매우 중요한 역할을 한다. computed는 값이 변경되지 않으면 캐시된 값을 반환하여 성능을 최적화한다. 이 동작 흐름을 이해하는 것이 중요하다.

  1. 처음 호출: computed 내부의 getter 함수가 실행되어 값을 계산하고 캐싱
  2. 값이 변경되지 않으면: computed를 다시 호출해도 캐시된 값을 반환
  3. 값이 변경되면: 캐시가 삭제되고, 다시 계산하여 새로운 값을 캐시
const test = computed(() => {
  console.log('test computed 실행')
  return count.value * 2
})
  • 위 코드에서 count 값이 변경되지 않으면 test를 여러 번 호출해도 계산을 하지 않고 캐시된 값을 반환
  • count 값이 변경되면 test는 다시 계산하고 그 값을 캐싱

 

5. 컴포넌트에서 Pinia 사용 예시

컴포넌트에서 Pinia를 사용하려면 먼저 store를 import하고, store를 사용한다.

컴포넌트 예시

<script setup>
import { useCount1Store } from '@/stores/counter'

const countStore = useCount1Store()

function addFive() {
  countStore.increment(5)
}
</script>

<template>
  <div>
    <p>state.count: {{ countStore.state.count }}</p>
    <p>ref count: {{ countStore.count }}</p>
    <p>computed test: {{ countStore.test }}</p>
    <button @click="addFive">+5</button>
    <button @click="countStore.plus">plus 1</button>
  </div>
</template>

 

  • useCount1Store()로 store를 가져온다.
  • countStore.state.count는 state의 값을 가져온다.
  • countStore.test는 computed로 정의된 파생된 데이터를 가져온다.
  • increment()와 plus()는 각각 action으로 정의된 데이터 수정 함수이다.

6. Pinia와 Router, Axios 통합 예시

Axios를 통한 데이터 호출 예시

src/stores/data.js

import { defineStore } from 'pinia'
import { ref } from 'vue'
import axios from 'axios'

export const useDataStore = defineStore('data', () => {
  const data = ref(null)
  const isLoading = ref(true)
  const error = ref(null)

  // API 호출하는 action
  const fetchData = async () => {
    try {
      const response = await axios.get('https://api.example.com/data')
      data.value = response.data
    } catch (err) {
      error.value = err
    } finally {
      isLoading.value = false
    }
  }

  // 첫 렌더링 시 API 호출
  fetchData()

  return { data, isLoading, error, fetchData }
})

컴포넌트에서 API 데이터 사용

<script setup>
import { useDataStore } from '@/stores/data'

const dataStore = useDataStore()
</script>

<template>
  <div v-if="dataStore.isLoading">로딩 중...</div>
  <div v-if="dataStore.error">에러 발생: {{ dataStore.error }}</div>
  <div v-else>
    <pre>{{ dataStore.data }}</pre>
  </div>
</template>

 

  • axios를 사용하여 API에서 데이터를 받아옴
  • fetchData 함수는 async로 데이터를 받아오고, isLoading과 error 상태를 관리
  • 컴포넌트에서 dataStore를 통해 데이터를 표시함

Router와 함께 사용 예시

import { useRouter } from 'vue-router'
import { defineStore } from 'pinia'

export const useAuthStore = defineStore('auth', () => {
  const router = useRouter()

  const login = async (credentials) => {
    try {
      const response = await axios.post('/login', credentials)
      if (response.data.success) {
        router.push('/dashboard')
      }
    } catch (error) {
      console.error('로그인 실패', error)
    }
  }

  return { login }
})