Frontend/Vue 3

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

SOLYI 2023. 11. 20. 08:00

 

목차

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()
반응형