배경
Vuex를 사용하여 쇼핑몰 애플리케이션에서 장바구니를 구현하고 있었다.
장바구니 페이지는 사용자가 장바구니에 담아놓은 상품들이 있고, 각 상품에는 해당 상품을 장바구니에서 지울 수 있는 삭제 버튼이 존재한다.
컴포넌트는 [부모 컴포넌트]장바구니(CartPage) -> [자식 컴포넌트]상품(CartItem) 방식으로 설계를 해두었고,
상품(CartItem) 내부에 구현된 삭제 버튼을 클릭할 때, Store에서 해당 상품을 제거하는 이벤트가 발생하게 된다.
기존에는 삭제 버튼을 클릭하는 경우, 부모 컴포넌트로 이벤트를 emit하여 부모 컴포넌트에서 Store 업데이트 작업을 처리하도록 구현했었다.
그러나, 코드 리뷰를 통해서 해당 기능은 Vuex를 사용하고 있으므로 부모 컴포넌트로 event를 전달하기보다, 자식 컴포넌트에서 바로 Store 업데이트를 처리하도록 수정하는 것이 좋겠다고 판단하게 되었다.
따라서 아래와 같이 상품 컴포넌트(CartItem)에서 바로 state를 변경하는 작업을 실행하도록 리팩토링을 하고 있었다.
// CartItem.vue
<template>
<div class="cart-item-container" data-test="cart-item">
...
<p class="delete-btn-container" @click="removeItem">
<!-- fontawesome icon -->
<i :class="['fas', 'fa-ban']" data-test="delete-button"></i>
</p>
...
</div>
</template>
removeItem() {
this.$store.commit('delCartItem', this.productNo);
}
// store/index.js
import { createStore } from 'vuex';
import items from './CartData';
export default createStore({
state: {
cartItems: [],
},
mutations: {
setCartItems(state) {
state.cartItems = items;
},
// 이 부분에서 장바구니 상품을 제거하게 된다.
delCartItem(state, id) {
state.cartItems = state.cartItems.filter((el) => el.product_no !== id);
},
},
});
그러다 문뜩 언제 action을 사용해서 state를 변경하는건지 궁금해졌다. mutation과 action 모두 state를 변경하기 때문이다.
Actions
Vuex 공식문서를 보면 언제 action을 사용하는지 설명한다.
This may look silly at first sight: if we want to increment the count, why don't we just call store.commit('increment') directly? Remember that mutations have to be synchronous. Actions don't. We can perform asynchronous operations inside an action
state는 오로지 mutation을 통해서만 변경될 수 있다.
mutation은 동기적이다.
비동기적인 기능은 action을 사용하여 mutation을 실행하여 구현할 수 있다.
actions: {
incrementAsync ({ commit }) {
setTimeout(() => {
commit('increment')
}, 1000)
}
}
이 코드와 같이 action을 사용하면 1초 뒤에 increment mutation을 실행하는 비동기 구문을 작성할 수 있게 된다.
// assuming `getData()` and `getOtherData()` return Promises
actions: {
async actionA ({ commit }) {
commit('gotData', await getData())
},
async actionB ({ dispatch, commit }) {
await dispatch('actionA') // wait for `actionA` to finish
commit('gotOtherData', await getOtherData())
}
}
이러한 예시도 존재한다.
결론
stackoverflow를 참고하여 mutation과 action의 사용에 대한 구분을 짓게 되었다.
- mutation이 state를 변경할 수 있는 유일한 방법이다.
- mutation은 비즈니스 로직에 관여하지 않고, state의 변경에만 집중한다.
- action은 비즈니스 로직을 담당한다.
- action은 동시에 1개 이상의 mutation을 commit할 수 있고, state의 변경에는 관여하지 않고 오로지 mutation을 다루며 비즈니스 로직 구현에만 집중한다.
reference
Vuex 공식문서: https://vuex.vuejs.org/guide/mutations.html
stackoverflow - Vuex Action vs Mutations: https://stackoverflow.com/questions/39299042/vuex-action-vs-mutations