모듈
스토어를 여러 파일로 분할하는 것만으로는 충분하지 않은 경우, 스토어를 Vuex 모듈로 나눠 관리 할 수 있습니다.
NOTE
개별 Vuex 모듈은 로컬 상태, 뮤테이션, 액션, 게터를 가질 수 있습니다.
모듈 분리
스토어에서 books
모듈과 관련된 것을 추려내어 books.js
모듈 파일을 만듭니다.
// store/modules/books.js
import shop from '@/api/shop';
export default {
state: {
books: [],
},
getters: {
availableBooks(state) {
return state.books.filter(book => book.inventory > 0)
},
checkOutOfStock(state) {
return book => {
return book.inventory === 0;
}
},
},
actions: {
fetchBooks({commit}) {
return new Promise((resolve, reject) => {
shop.getBooks(books => {
commit("setBooks", books)
resolve();
});
});
},
},
mutations: {
setBooks(state, books) {
state.books = books
},
decreamentBookInventory(state, book) {
book.inventory--;
},
}
}
cart
모듈 또한 추려내어 cart.js
파일을 생성합니다.
// store/modules/cart.js
import shop from '@/api/shop';
export default {
state: {
cart: [],
purchageStatus: '',
},
getters: {
cartBooks(state) {
return state.cart.map(cartItem => {
const book = state.books.find(book => book.id === cartItem.id);
return {
name: book.name,
price: book.price,
quantity: cartItem.quantity
}
});
},
cartTotal(state, getters) {
return getters.cartBooks
.reduce((total,cartItem) => total + cartItem.price * cartItem.quantity, 0);
}
},
actions: {
addBookToCart({state, getters, commit}, book) {
if (!getters.checkOutOfStock(book)) {
const cartItem = state.cart.find(item => item.id === book.id);
if (!cartItem) {
commit('pushBookToCart', book.id);
} else {
commit('increamentBookQuantity', cartItem);
}
commit('decreamentBookInventory', book);
}
},
purchage({state, commit}) {
shop.buyBooks(
state.cart,
() => {
commit('emptyCart');
commit('notifyStatus', '성공');
},
() => {
commit('notifyStatus', '실패');
}
);
},
},
mutations: {
emptyCart(state) {
state.cart = [];
},
notifyStatus(state, status) {
state.purchageStatus = status;
},
pushBookToCart(state, bookId) {
state.cart.push({
id: bookId,
quantity: 1
})
},
increamentBookQuantity(state, cartItem) {
cartItem.quantity++;
},
}
}
분리 관리되는 각 모듈을 스토어에서 modules
속성에 설정합니다.
// store/index.js
import Vue from 'vue';
import Vuex from 'vuex';
import books from './modules/books';
import cart from './modules/cart';
Vue.use(Vuex);
export default new Vuex.Store({
modules: {
books, cart
}
});
모듈 분리에 따른 코드 리팩토링
모듈을 분리한 후, Vue Devtools 개발 도구 Vuex 탭을 살펴보면 스토어에 병합된 모듈의 각 스테이트는 모듈 객체에 종속되어 있는 것을 볼 수 있습니다. 분리된 모듈은 로컬 스테이트를 가지기 때문입니다.
store.state.books.books
이러한 이유로 컴포넌트가 스토어의 각 스테이트를 올바로 찾지 못해 애플리케이션이 정상적으로 작동하지 않게 됩니다. 스토어의 스테이트를 올바로 접근할 수 있도록 코드를 변경해야 정상 작동됩니다.
// components/BookList.vue
export default {
name: 'BookList',
computed: {
...mapState({
books: state => state.books.books
}),
},
// ...
}
그런데 state.books.books
는 이름이 중복적인 느낌이 강하니 books.js
모듈을 수정해봅니다.
// store/modules/books.js
import shop from '@/api/shop';
export default {
state: {
items: [],
// books: []
},
getters: {
availableBooks(state) {
return state.items.filter(book => book.inventory > 0)
// return state.books.filter(book => book.inventory > 0)
}
},
// ...
mutations: {
setBooks(state, books) {
state.items = books
// state.books = books
},
// ...
}
};
앞서 중복되었던 이름 대신, state.books.items
를 사용해 이질감을 줄일 수 있습니다.
// components/BookList.vue
export default {
name: 'BookList',
computed: {
...mapState({
books: state => state.books.items
// books: state => state.books.books
}),
},
// ...
}
cart.js
또한 동일한 방법으로 수정합니다.
// store/modules/cart.js
export default {
state: {
items: [],
// cart: [],
purchageStatus: '',
},
getters: {
cartBooks(state, getters) {
return state.items.map(cartItem => {...});
// return state.cart.map(cartItem => { ... });
},
// ...
},
actions: {
addBookToCart({state, getters, commit}, book) {
if (!getters.checkOutOfStock(book)) {
const cartItem = state.items.find(item => item.id === book.id);
// const cartItem = state.cart.find(item => item.id === book.id);
// ...
}
},
purchage({state, commit}) {
shop.buyBooks(
state.items,
// state.cart,
// ...
);
},
},
mutations: {
emptyCart(state) {
state.items = [];
// state.cart = [];
},
// ...
pushBookToCart(state, bookId) {
state.items.push({...});
// state.cart.push({ ... });
},
// ...
}
}
쇼핑 카트에 추가 시 오류 발생
화면에 출력된 도서 목록의 '카트에 추가' 버튼을 누르니 다음과 같은 오류가 발생합니다.
오류 발생!
Cannot read property 'find' of undefined
해당 오류는 사용자가 선택한 도서를 쇼핑 카트에 추가할 때 cart
모듈의 cartBooks
게터가
books
모듈에 접근해야 하는데 cart 모듈의 state
는 로컬 스테이트로 books
모듈에
접근할 수 없어 발생한 오류입니다.
이 문제를 해결하려면 스토어의 루트 스테이트를 사용해야 합니다. Vuex 게터는 3번째 인자로
rootState
를 전달 받습니다. 이를 통해 books
에 접근하면 문제가 해결됩니다.
// store/modules/cart.js
getters: {
cartBooks(state, getters, rootState) {
return state.items.map(cartItem => {
const book = rootState.books.items.find(book => book.id === cartItem.id);
// const book = state.books.find(book => book.id === cartItem.id);
return { ... }
}