본문 바로가기
Frontend/Vue 3

Pinia 기본 사용 방법 (vuex의 새로운 이름)

by SOLYI 2023. 11. 20.

 

목차

Vue 기본 구조

Pinia 기본 구조

State (Vue)

State (Pinia)

Getters (Vue)

Getters (Pinia)

Actions (Vue)

Actions (Pinia)

 

 

Vue 기본 구조

  • 기본 구조 설명 - Composition API 방식( 다른 방식으로는 Option API 방식이있음 export default{} )
<script setup>
// 사용하려는 store를 import
import { useTodosStore } from '@/stores/todos'

// 사용 선언
const todos = useTodosStore()

// --- state의 값을 반응형으로 참조하는 방법
const { todos, count } = storeToRefs(todos)

// --- action은 구조분해하여 사용할 수 있음.
const { increment } = todos 

// --- state의 값 수정 방법 3가지 
// 1. 값 수정
counter.count++
// 2. $patch 이용
counter.$patch({ count: counter.count + 1 })
// 3. action 이용
counter.increment()

</script>

<template>
  <!-- Store에 직접 state에 접근 -->
  <div>Current Count: {{ counter.count }}</div>
</template>

 

Pinia 기본 구조

  • 기본 구조 설명 Option Store방식
    • 기존 방식처럼 state, getters, actions를 직관적으로 표현한다.
    • stores/todos-store.js
    • import { defineStore } from 'pinia'
      
      export const useTodosStore = defineStore('todos', {  //-- store의 이름 
        state: () => ({
          /** @type {{ text: string, id: number, isFinished: boolean }[]} */
          todos: [],
          /** @type {'all' | 'finished' | 'unfinished'} */
          filter: 'all',
          // type will be automatically inferred to number
          nextId: 0,
      	count: 0 
        }),
        getters: {
          finishedTodos(state) {
            return state.todos.filter((todo) => todo.isFinished)
          },
          unfinishedTodos(state) {
            return state.todos.filter((todo) => !todo.isFinished)
          },
          
          filteredTodos(state) {
            if (this.filter === 'finished') {
              return this.finishedTodos
            } else if (this.filter === 'unfinished') {
              return this.unfinishedTodos
            }
            return this.todos
          },
        },
        actions: {
          increment() {
            this.count++
          },
          addTodo(text) {
            this.todos.push({ text, id: this.nextId++, isFinished: false })
          },
        },
      })

 

State (Vue)

  • 기본적으로 인스턴스를 통해 state에 접근하여 읽고 쓸 수 있음
    • 단, state에 먼저 정의가 되어있어야함.
    const store = useStore()
    store.count++
    

 

  • 초기화 (Option Store방식만 가능, Setup Store 방식은 직접 생성해야함.)
  • const store = useStore()
    store.$reset()

 

  • state 변경
    1. 직접 변경
      const store = useStore()
      store.count++​
    2. patch사용
      const store = useStore()
      store.$patch({
        count: store.count + 1,
        age: 120,
        name: 'DIO',
      })​

       
    3. array를 수정(push, remove, splice 등) 해야하는 경우라면 mutation을 그룹화 하는 함수도 가능함
      const store = useStore()
      store.$patch((state) => {
        state.items.push({ name: 'shoes', quantity: 1 })
        state.hasChanged = true
      })​
  • state 변경시 주의
  • // ❌ $state 를 대신할 수 없음
    store.$state = { count: 24 }
    // ⭕ 내부적으로 $patch()를 호출
    store.$patch({ count: 24 })
  • state 변경 추적 (subscribe, 구독)
  • <script setup>
    const someStore = useSomeStore()
    
    // 이 추적은 component가 unmount된 후에도 유지됨
    someStore.$subscribe(callback, { detached: true })
    </script>

 

 

State (Pinia)

  • state 의 기본 구조 (Option Store 방식)
  • state: () => {
      return {
        // 처음에는 비어있는 list의 경우
        userList: [] as UserInfo[],
        // 아직 load되지 않은 데이터의 경우
        user: null as UserInfo | null,
      }
    },
    interface UserInfo {
      name: string
      age: number
    }

 

  • State 인터페이스를 이용하는 경우 (Option Store 방식)
  • interface State {
      userList: UserInfo[]
      user: UserInfo | null
    }
    
    export const useUserStore = defineStore('user', {
      state: (): State => {
        return {
          userList: [],
          user: null,
        }
      },
    })
    
    interface UserInfo {
      name: string
      age: number
    }

 

  • state 변경 추적 (subscribe, 구독)
  • import { MutationType } from 'pinia'
    
    cartStore.$subscribe((mutation, state) => {
      mutation.type // 'direct' | 'patch object' | 'patch function'
      // same as cartStore.$id
      mutation.storeId // 'cart'
      // only available with mutation.type === 'patch object'
      mutation.payload // patch object passed to cartStore.$patch()
    
      // 변경될 때마다 전체 상태를 로컬 스토리지에 유지
      localStorage.setItem('cart', JSON.stringify(state))
    })​
     

 

 

 

Getters (Vue)

  • getters에 접근
  • <script setup>
    import { useCounterStore } from './counterStore'
    
    const store = useCounterStore()
    </script>
    
    <template>
      <p>Double count is {{ store.doubleCount }}</p>
    </template>

 

  • getters에 인수(argument) 전달
    • computed 속성이므로 매개변수를 전달할 수는 없으나, 허용하는 방법은 있음.
    <script setup>
    import { storeToRefs } from 'pinia'
    import { useUserListStore } from './store'
    
    const userList = useUserListStore()
    
    // <script setup> 내에서 
    // getUserById.value에 접근 해야함.
    const { getUserById } = storeToRefs(userList)
    </script>
    
    <template>
      <p>User 2: {{ **getUserById(2)** }}</p>
    </template>
    
    • 이 방법을 사용하면 getter는 더이상 cashed 되지 않음, 단순히 호출하는 함수

 

Getters (Pinia)

  • vue의 computed, vuex의 getters와 유사
  • getters 의 기본 구조
  • export const useCounterStore = defineStore('counter', {
      state: () => ({
        count: 0,
      }),
      getters: {
        // 반환 타입을 자동으로 number로 추론함
        doubleCount(state) {
          return state.count * 2
        },
        // 반환 타입은 "반드시" 명시적으로 설정해야함
        doublePlusOne(): number {
          // 전체 store에 대한 자동 완성 및 typings ✨
          return this.doubleCount + 1
        },
      },
    })
  • getters에 인수(argument) 전달
    • computed 속성이므로 매개변수를 전달할 수는 없으나, 허용하는 방법은 있음.
    export const useStore = defineStore('main', {
      getters: {
        getUserById: (state) => {
          return (userId) => state.users.find((user) => user.id === userId)
        },
      },
    })
    • 이 방법을 사용하면 getter는 더이상 cashed 되지 않음, 단순히 호출하는 함수
  • 다른 store의 getter 에 access
    • import { useOtherStore } from './other-store'
      
      export const useStore = defineStore('main', {
        state: () => ({
          // ...
        }),
        getters: {
          otherGetter(state) {
            const otherStore = useOtherStore()
            return state.localData + otherStore.data
          },
        },
      })

Actions (Vue)

  • actions의 기본 구조
  • <script setup>
    const store = useCounterStore()
    // store에서 action을 호출
    store.randomizeCounter()
    </script>
    
    <template>
      <button @click="store.randomizeCounter()">Randomize</button>
    </template>
  •  

 

  • action 변경 추적 (subscribe, 구독)
  • <script setup>
    const someStore = useSomeStore()
    
    // 이 구독은 component가 unmount된 후에도 유지 됨
    someStore.$onAction(callback, true)
    </script>

 

Actions (Pinia)

 

  • actions 의 기본 구조
  • export const useCounterStore = defineStore('counter', {
      state: () => ({
        count: 0,
      }),
      actions: {
        // this에 의존하므로 화살표 함수 사용할 수 없음
        increment() {
          this.count++
        },
        randomizeCounter() {
          this.count = Math.round(100 * Math.random())
        },
      },
    })

 

  • 다른 store의 actions 접근
  • import { useAuthStore } from './auth-store'
    
    export const useSettingsStore = defineStore('settings', {
      state: () => ({
        preferences: null,
        // ...
      }),
      actions: {
        async fetchUserPreferences() {
          const auth = useAuthStore()
          if (auth.isAuthenticated) {
            this.preferences = await fetchPreferences()
          } else {
            throw new Error('User must be authenticated')
          }
        },
      },
    })

 

  • action 변경 추적 (subscribe, 구독)
  • const unsubscribe = someStore.$onAction(
      ({
        name, // action의 이름
        store, // store 인스턴스(예시:someStore)
        args, // action에 전달되는 매개변수 배열
        after, // action 반환 혹은 resolve 후의 hook
        onError, // action throw 혹은 rejenct 시의 hook
      }) => {
        // 특정 action 호출에 대한 공유 변수(shared variable)
        const startTime = Date.now()
        // 이 코드는 store에 대한 action 실행 전 트리거 됨
        console.log(`Start "${name}" with params [${args.join(', ')}].`)
    
        // 이 코드는 action이 성공하고 완전히 실행된 후에 트리거 됨
        // promised를 반환 할때까지 기다림
        after((result) => {
          console.log(
            `Finished "${name}" after ${
              Date.now() - startTime
            }ms.\nResult: ${result}.`
          )
        })
    
        // action throw 혹은 rejenct 시에 실행
        onError((error) => {
          console.warn(
            `Failed "${name}" after ${Date.now() - startTime}ms.\nError: ${error}.`
          )
        })
      }
    )
    
    // listner를 수동으로 제거
    unsubscribe()
반응형