Pinia

Pinia 4

Hoon1994 2022. 9. 7. 11:22

Pinia

Pinia Actions

이번 포스팅에서는 Pinia의 Actions에 대해서 다룰 예정이다. 

공식문서에 나와 있는 예제는 아래와 같다. 예제 코드를 먼저 보자.

 

export const useCounterStore = defineStore('counter', {
  state: () => ({
    count: 0,
  }),
  actions: {
    // since we rely on `this`, we cannot use an arrow function
    increment() {
      this.count++
    },
    randomizeCounter() {
      this.count = Math.round(100 * Math.random())
    },
  },
})

 

defineStore 함수 내부에 actions를 통해서 함수를 등록할 수 있다. this로 state, getters 등에
접근을 하기 위해서는 화살표 함수가 아닌 일반 함수를 사용해야 한다.

 

Actions에서는 API 호출 및 다른 작업을 할 수 있다고 공식문서에 설명하고 있다. 아래는 API를 호출한 케이스의 예제 코드다.

 

import { mande } from 'mande'

const api = mande('/api/users')

export const useUsers = defineStore('users', {
  state: () => ({
    userData: null,
    // ...
  }),

  actions: {
    async registerUser(login, password) {
      try {
        this.userData = await api.post({ login, password })
        showTooltip(`Welcome back ${this.userData.name}!`)
      } catch (error) {
        showTooltip(error)
        // let the form component display the error
        return error
      }
    },
  },
})

 

확실히, Mutation을 사용하지 않고 바로 this.state를 통해 데이터를 전달하다보니 코드가 간결해진 느낌이긴하다.

Vuex에서 Mutation을 사용할 때 Vue Devtools를 통해 데이터 이동을 관찰할 수 있었는데, Pinia에서는 Mutation이 없어서 

만약 관찰하기를 원한다면 어떻게 해야하지? 생각하고 Vue Devtools를 봤는데, 걱정할 필요가 없었다.

 

 

Pinia에서 코드를 작성할 땐 Mutation을 작성하지 않았지만, Devtools에서는 mutation 이름으로 전달된 값이 추적이 가능하다.

이거는 매우 유용하게 사용이 가능할 것 같다.

 

추가로 start, set, end 이렇게 구분이 되어 있는데 start는 Action이 시작되었을 때, Store를 보여주고,
set은 전달된 값 및 스토어 정보, end는 Action이 종료되었을 때의 Store 정보를 보여준다.

별거 아닌 것 같아 보일 수 있어도 실무에서 달달하게 사용할 수 있을 것 같다. 

 

아래는 Actions를 Component에서 어떻게 사용하는지 보여주는 예시 코드다.

 

export default defineComponent({
  setup() {
    const store = useCounterStore()
    // call the action as a method of the store
    store.randomizeCounter()

    return {}
  },
})

 

또한 특정 Store의 Actions를 구독할 수 있다.

 

const unsubscribe = someStore.$onAction(
  ({
    name, // name of the action
    store, // store instance, same as `someStore`
    args, // array of parameters passed to the action
    after, // hook after the action returns or resolves
    onError, // hook if the action throws or rejects
  }) => {
    // a shared variable for this specific action call
    const startTime = Date.now()
    // this will trigger before an action on `store` is executed
    console.log(`Start "${name}" with params [${args.join(', ')}].`)

    // this will trigger if the action succeeds and after it has fully run.
    // it waits for any returned promised
    after((result) => {
      console.log(
        `Finished "${name}" after ${
          Date.now() - startTime
        }ms.\nResult: ${result}.`
      )
    })

    // this will trigger if the action throws or returns a promise that rejects
    onError((error) => {
      console.warn(
        `Failed "${name}" after ${Date.now() - startTime}ms.\nError: ${error}.`
      )
    })
  }
)

// manually remove the listener
unsubscribe()

 

&onAction는 실제로 Action이 호출되기 전에 먼저 실행된다. 

after 함수를 통해 작업이 마무리된 후 추가 작업을 할 수 있고, onError를 통해 추가적인 에러 핸들링이 가능하다.

 

&onAction는 remove하는 함수를 리턴함으로, 구독을 삭제할 수 있다. 

해당 함수가 추가된 Component에 바인딩되므로, Component가 해제될 경우 자동으로 삭제된다고 한다.

삭제되기를 원하지 않을 경우, $onAction의 2번째 인자로 값을 전달하면 된다. 

 

export default {
  setup() {
    const someStore = useSomeStore()

    // this subscription will be kept even after the component is unmounted
    someStore.$onAction(callback, true)

    // ...
  },
}

 

 

Pinia... 꽤나 마음에 드는 녀석이다.