Vue - Pinia

h.Im·2024년 9월 14일
0

Vue 기초

목록 보기
7/28
post-thumbnail

상태 관리

상태 관리(State Management)란 애플리케이션에서 여러 컴포넌트나 모듈들이 공유하는 상태(state)를 일관되게 관리하는 방법을 말합니다. 이 상태는 보통 사용자 인터페이스(UI)의 일부나 비즈니스 로직과 관련된 데이터를 의미하며, 애플리케이션이 복잡해질수록 이를 효율적으로 관리하는 것이 중요해집니다.

상태 관리 패턴

  • 로컬 상태(Local State): 컴포넌트 자체에서 관리하는 상태입니다. 작은 규모의 애플리케이션에서는 컴포넌트 내부에서 상태를 관리하는 방식이 간단하고 효율적입니다.
  • 전역 상태 관리(Global State Management): 여러 컴포넌트가 상태를 공유해야 하는 경우, 상태를 전역적으로 관리합니다. Vue에서는 Vuex나 Pinia와 같은 상태 관리 라이브러리를 사용해 상태를 관리할 수 있습니다.

Pinia란

Pinia는 Vue 3의 공식 상태 관리 라이브러리로, Vuex의 대안으로 설계되었습니다. Vuex와 같은 목적을 가지고 있지만, 더 가볍고 사용하기 쉬운 API를 제공합니다. Pinia는 Vue 3의 Composition API를 기반으로 설계되어, Vue 3에서 상태 관리 작업을 보다 직관적이고 효율적으로 할 수 있도록 해줍니다.

Pinia의 핵심 개념

  • Store(스토어)
    Pinia에서 상태는 스토어라는 곳에 저장됩니다. 스토어는 상태(state), actions(액션), getters(게터)로 구성됩니다.
    • State: 애플리케이션에서 관리하는 상태 데이터를 정의합니다.
    • Actions: 상태를 변경하거나 비동기 작업을 수행할 수 있는 메서드를 정의합니다. Vuex와 달리 mutations가 없어, actions에서 직접 상태를 수정할 수 있습니다.
    • Getters: 상태를 기반으로 계산된 값을 반환하는 메서드로, Vuex의 getters와 유사합니다.

Pinia 사용

Store 정의

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

// 'useCounterStore'라는 이름의 스토어 정의
export const useCounterStore = defineStore('counter', {
  state: () => ({
    count: 0,
  }),
  actions: {
    increment() {
      this.count++;
    },
    decrement() {
      this.count--;
    },
  },
  getters: {
    doubleCount: (state) => state.count * 2,
  },
});

Store 사용

<template>
  <div>
    <p>{{ count }}</p>
    <p>{{ doubleCount }}</p>
    <button @click="increment">Increment</button>
    <button @click="decrement">Decrement</button>
  </div>
</template>

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

const counterStore = useCounterStore();

const { count, doubleCount } = counterStore;
const { increment, decrement } = counterStore;
</script>

사용 방법이 직관적이기 때문에 사용에 어려움은 없을 것 같아, 짚고 넘어가야 할 부분에 대해서만 정리하도록 하겠습니다.
doubleCount는 이전에 배웠던 computed와 동일하게, 캐싱도 발생합니다.


Pinia에 저장해야 할 것, 저장하지 말아야 할 것

저장하면 좋은 데이터

  • 전역적으로 공유되는 상태
    사용자 인증 정보 (Authentication)는 애플리케이션 전반에서 자주 사용됩니다. 여러 컴포넌트에서 사용되는 데이터를 Pinia에 저장하면 상태를 쉽게 공유하고 관리할 수 있습니다.
  • 애플리케이션의 비즈니스 로직에 중요한 데이터
    쇼핑 카트 등은 여러 페이지(상품 목록, 결제 페이지 등)에서 참조되고, 사용자가 앱을 탐색하는 동안 유지되어야 합니다. Pinia에 저장하면 상태를 전역적으로 쉽게 관리할 수 있습니다.
  • 서버에서 불러온 데이터 (Fetched Data)
    한 번 불러온 데이터를 여러 컴포넌트에서 사용해야 하거나, 애플리케이션 전체에서 사용되는 경우 Pinia에 저장하는 것이 좋습니다(조회할 때마다 상태가 결과가 변할 만한 데이터 제외). 이렇게 하면 같은 데이터를 여러 번 불러오는 일을 방지하고 상태 관리가 용이해집니다.
  • 애플리케이션 설정 (App Settings)
    테마나 언어 설정과 같은 데이터는 전역에서 참조되며, 애플리케이션 전반에 걸쳐 일관성을 유지해야 합니다. Pinia에 저장하면 이러한 설정을 쉽게 적용할 수 있습니다.
  • 현재 활성화된 메뉴나 탭
    사용자 인터페이스에서 여러 화면 간의 상태를 전역적으로 관리할 때 유용합니다. 현재 활성화된 탭이나 메뉴 상태는 Pinia에 저장하면 페이지 간 이동 시 상태를 쉽게 유지할 수 있습니다.

저장하지 말아야 할 데이터

위에 정리한 내용들 외에, 아래 데이터들은 전역적으로 관리할 필요가 없기 때문에 Pinia에 저장하여 사용하는 것을 지양해야 합니다.

  • 일시적인 UI 상태
  • 폼 데이터(사용자의 일시적인 input 데이터)
  • 매우 짧은 생명주기의 상태
  • 컴포넌트 내에서만 사용되는 계산된 값

storeToRefs

storeToRefs는 Pinia에서 제공하는 헬퍼 함수로, 반응형 스토어 상태와 getter를 ref로 변환하는 역할을 합니다. Vue 3의 Composition API에서 Pinia 스토어를 사용할 때, 반응성을 쉽게 관리할 수 있게 도와줍니다. storeToRefs에 대해 알아보기 전에, storeToRefs가 없다면 아래와 같이 코드를 작성해야 합니다.

import { useMyStore } from '@/stores/myStore'
import { toRef, toRefs } from 'vue'

export default {
  setup() {
    const store = useMyStore()

    // storeToRefs 없이 상태와 getter를 ref로 변환
    const count = toRef(store, 'count') // 상태를 toRef로 변환
    const doubleCount = computed(() => store.doubleCount) // getter는 computed로 직접 생성

    return { count, doubleCount }
  }
}

useMyStore store에서 count 값을 꺼내 count라는 반응형 데이터로 변환한 것을 볼 수 있습니다. 구조 분해 할당을 사용하기 위해 toRef가 사용 되었습니다. storeToRefs를 사용하면 위 코드가 아래와 같이 개선됩니다.

import { useMyStore } from '@/stores/myStore'
import { storeToRefs } from 'pinia'

export default {
  setup() {
    const store = useMyStore()

    // storeToRefs로 상태와 getter를 ref로 변환
    const { count, doubleCount } = storeToRefs(store)

    return { count, doubleCount }
  }
}

mapStores

mapStores도 자주 사용되는 pinia의 함수 중 하나입니다. Pinia에서 제공하는 헬퍼 함수로, Vue 컴포넌트의 computed 속성이나 methods에서 여러 스토어를 간편하게 가져와 사용하기 위해 사용됩니다. 스토어의 상태(state), getter, actions를 각각 불러오는 것보다 훨씬 간편하게 한 번에 가져올 수 있는 장점이 있습니다. mapStores또한 mapStores를 사용하는 경우와 사용하지 않는 경우 어떻게 코드가 달라지는지 확인해 보겠습니다.

mapStores를 사용하지 않은 경우

import { useCounterStore } from '@/stores/counter'
import { useUserStore } from '@/stores/user'

export default {
  setup() {
    const counterStore = useCounterStore()
    const userStore = useUserStore()

    // 스토어의 상태와 메서드를 개별적으로 사용
    const count = computed(() => counterStore.count)
    const userName = computed(() => userStore.name)

    const incrementCount = () => {
      counterStore.increment()
    }

    return {
      count,
      userName,
      incrementCount,
    }
  }
}
  • 각 스토어(counterStore, userStore)를 개별적으로 useStore()로 불러오고, 필요한 상태(state)나 actions를 일일이 computed 또는 메서드로 연결합니다.
  • 코드가 장황해지고, 여러 스토어를 사용할 때 반복 작업이 많아질 수 있습니다.

mapStores를 사용한 경우

import { mapStores } from 'pinia'
import { useCounterStore, useUserStore } from '@/stores'

export default {
  setup() {
    // mapStores로 여러 스토어를 한 번에 가져옴
    const { counterStore, userStore } = mapStores(useCounterStore, useUserStore)

    const incrementCount = () => {
      counterStore.increment()
    }

    return {
      count: counterStore.count,  // 직접 스토어 상태에 접근 가능
      userName: userStore.name,    // 직접 스토어 상태에 접근 가능
      incrementCount,
    }
  }
}
  • mapStores를 사용하면 여러 스토어를 한 번에 가져와 사용할 수 있습니다.
  • 상태(state), getter, actions 등을 별도로 computed로 감싸지 않고도 자동으로 반응형으로 사용할 수 있습니다.
  • 코드가 훨씬 간결해지고, 여러 스토어를 다룰 때 반복되는 코드를 줄일 수 있습니다.

$patch

  • Pinia 스토어에서 상태를 일괄적으로 업데이트할 때 사용하는 함수입니다.
  • reactive로 정의된 상태를 직접 수정하지 않고, 일괄적으로 여러 상태를 업데이트할 때 사용합니다.

$patch 없이 상태 업데이트

import { defineStore } from 'pinia'

export const useUserStore = defineStore('user', {
  state: () => ({
    name: 'John',
    age: 30,
    address: {
      city: 'Seoul',
      street: '123 Main St'
    }
  }),
  actions: {
    updateUser(name, age, city, street) {
      this.name = name
      this.age = age
      this.address.city = city
      this.address.street = street
    }
  }
})

$patch를 이용한 일괄 상태 업데이트

import { defineStore } from 'pinia'

export const useUserStore = defineStore('user', {
  state: () => ({
    name: 'John',
    age: 30,
    address: {
      city: 'Seoul',
      street: '123 Main St'
    }
  }),
  actions: {
    updateUser(data) {
      this.$patch(data)
    }
  }
})

$reset

$reset은 Pinia 스토어의 상태를 초기 상태로 복원하는 메서드입니다. 이를 사용하면 스토어를 초기화할 수 있어, 특정 상황에서 상태를 재설정하는 데 유용합니다.

$reset 없이 상태 초기화

import { defineStore } from 'pinia'

export const useUserStore = defineStore('user', {
  state: () => ({
    name: 'John',
    age: 30,
    address: {
      city: 'Seoul',
      street: '123 Main St'
    }
  }),
  actions: {
    resetUser() {
      this.name = 'John'
      this.age = 30
      this.address.city = 'Seoul'
      this.address.street = '123 Main St'
    }
  }
})

$reset을 사용한 상태 초기화

import { defineStore } from 'pinia'

export const useUserStore = defineStore('user', {
  state: () => ({
    name: 'John',
    age: 30,
    address: {
      city: 'Seoul',
      street: '123 Main St'
    }
  }),
  actions: {
    resetUser() {
      this.$reset()
    }
  }
})

0개의 댓글