Vuex를 활용한 스토어, 전역 상태 관리가 궁금하다면?
2025.11.20 - [Web/Vue] - Vuex 기초: Vue 전역 상태 관리 (state, getters, mutations, actions, modules)
Vuex 기초: Vue 전역 상태 관리 (state, getters, mutations, actions, modules)
Vue에서 컴포넌트끼리 데이터를 주고 받는 방식이 궁금하다면?2025.11.19 - [Web/Vue] - Vue.js SFC, props, emit으로 부모-자식 데이터 흐름 잡기 Vue.js SFC, props, emit으로 부모-자식 데이터 흐름 잡기Vue가 뭔지,
sproutinghye.tistory.com
1. 왜 Vuex 모듈이 필요한가?
처음에는 store/index.js 하나에 모든 state, mutations, actions, getters를 몰아넣어도 괜찮습니다.
하지만 프로젝트가 커지면 이런 문제가 생길 수 있습니다.
- 유저, 상품, 장바구니, 알림 등 도메인별 상태가 한 파일에 다 섞여 있음
- 어떤 mutation이 어떤 도메인 것인지 찾기 힘듦
- SET_DATA, UPDATE_LIST 같은 이름이 여기저기 중복됨
이럴 때를 위해 Vuex에서는 store를 모듈(module)로 나누는 기능을 제공합니다.
단일 상태 트리를 사용하기 때문에 애플리케이션의 모든 상태가 하나의 큰 객체에 들어간다.
규모가 커지면 저장소가 비대해지므로, Vuex는 저장소를 모듈로 나눌 수 있도록 한다.
각 모듈은 자체 state, mutations, actions, getters, 심지어 중첩 모듈까지 가질 수 있다.
- Vuex 공식 문서
요약하면, 모듈은 작게 쪼갠 Vuex 스토어 조각이라고 생각하면 됩니다.
2. 기본적인 Vuex 모듈 구조
기본 모듈 예시는 이런 형태입니다.
// store/modules/user.js
const userModule = {
state: () => ({
profile: null,
token: null
}),
mutations: {
SET_PROFILE(state, profile) {
state.profile = profile
},
SET_TOKEN(state, token) {
state.token = token
}
},
actions: {
login({ commit }, payload) {
// 로그인 API 호출 후
commit('SET_TOKEN', payload.token)
}
},
getters: {
isLoggedIn(state) {
return !!state.token
}
}
}
export default userModule
포인트를 정리하자면 아래와 같습니다.
- 모듈도 독립된 state / mutations / actions / getters를 가진다.
- state는 함수로 두는 패턴이 권장된다. (재사용 및 SSR에서 안전)
그리고 루트 스토어에서 이렇게 등록합니다.
// store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
import user from './modules/user'
import cart from './modules/cart'
Vue.use(Vuex)
export default new Veux.Store({
modules: {
user,
cart
}
})
이렇게 하면 실제 state 트리는 다음처럼 구성합니다.
this.$store.state.user // user 모듈 state
this.$store.state.cart // cart 모듈 state
3. namespaced
여기서 한 단계 더 나가면 namespaced 모듈 개념이 등장합니다.
기본값으로는 namespaced: false 상태라,
모든 모듈의 getters / actions / mutations가 전역 네임스페이스를 공유합니다.
그렇게 되면 프로젝트가 커졌을 때 이런 문제가 생길 수 있습니다.
- user 모듈에도 SET_LIST
- product 모듈에서 SET_LIST
- 전역에서 commit('SET_LIST') 했을 때 어느 쪽 mutation인지 알 수 없음
이걸 해결하는 방법이 바로 모듈에 namespaced: true 옵션을 주는 것입니다.
즉, 모듈이 독립적이거나 재사용되기를 원할 때 해당 옵션을 사용하면 됩니다.
Vuex 공식 문서에서도 모듈을 독립적이고 재사용 가능하게 만들기 위해 namespaced 옵션을 적극적으로 사용할 것을 권장합니다.
// store/modules/user.js
export default {
namespaced: true,
state: () => ({
profile: null
}),
mutations: {
SET_PROFILE(state, profile) {
state.profile = profile
}
},
actions: {
async fetchProfile({ commit }) {
// ...
commit('SET_PROFILE', /* 응답 데이터 */)
}
},
getters: {
hasProfile(state) {
return !!state.profile
}
}
}
namespaced: true를 사용하면,
이 모듈의 액션, 뮤테이션, 게터를 부를 때 모듈 이름/키를 앞에 붙여서 사용하게 됩니다.
예를 들어 modules: { user }라고 등록했다면 컴포넌트에서는 아래와 같이 호출합니다.
// 액션 호출
this.$store.dispatch('user/fetchProfile')
// 뮤테이션 커밋
this.$store.commit('user/SET_RPOFILE', profile)
// 게터 사용
this.$store.getters('user/hasProfile')
코드가 다소 길어지긴 하지만,
어떤 모듈의 무엇을 호출하는지 명확하고
서로 다른 모듈에서 같은 이름의 mutation/action을 마음 편하게 쓸 수 있다는 장점이 있습니다.
4. 폴더 구조 예시
실무에서 자주 보는 Vuex 모듈 폴더 구조는 대략 이런 느낌입니다.
src/
store/
index.js
modules/
user.js
auth.js
product.js
cart.js
ui.js
각 파일은 해당 도메인의 상태와 로직만 책임지도록 나눕니다.
예를 들어,
- user.js: 사용자 프로필, 설정, 권한
- auth.js: 토큰, 로그인/로그아웃, 인증 관련 로직
- product.js: 상품 목록, 상세, 필터 상태
- cart.js: 장바구니 아이템, 수량, 합계
- ui.js: 모달 열림 여부, 로딩 스피너 등 UI 상태
이렇게 나누면,
필요한 도메인만 열어보면 되고,
관련 mutation/action을 찾기가 훨씬 쉬워지고
나중에 다른 프로젝트로 모듈을 통째로 옮기기도 편해집니다.
Vuex 러닝 가이드나 여러 블로그에서도 "도메인별 모듈 분리 + namespaced: true" 조합을 가장 일반적인 설계 패턴으로 소개합니다.
5. 컴포넌트에서 namespaced 모듈 사용하기
namespaced 모듈을 쓸 때, mapState, mapGetters, mapActions, mapMutations를 어떻게 쓰는지가 헷갈리기 쉽습니다.
따라서 namespaced 모듈 사용 시 map 헬퍼를 사용해서 모듈을 호출하는 예제를 봐보겠습니다.
예를 들어 user 모듈이 있다고 할 때,
import { mapState, mapGetters, mapActions } from 'vuex'
export default {
computed: {
// user 모듈의 state/profile, state/token 가져오기
...mapState('user', ['profile', 'token']),
// user 모듈의 hasProfile getter 사용
...mapGetters('user', ['hasProfile'])
},
methods: {
// user 모듈의 fetchProfile 액션
...mapActions('user', ['fetchProfile'])
}
}
첫 번째 인자로 모듈 네임스페이스 문자열('user')을 넘기면,
그 모듈 안의 state/getter/actions를 매핑해 줍니다.
6. 중첩 모듈 (nested modules)
모듈 안에 또 모듈을 넣는 중첩 모듈도 지원합니다.
const moduleA = {
namespaced: true,
state: () => ({ /* ... */ })
modules: {
nested: {
namespaced: true,
state: () => ({ /* ... */ }),
// ...
}
}
}
스토어에 등록했을 때,
modules: {
a: moduleA
}
이렇게 되어 있으면,
중첩 모듈의 액션을 호출할 때는 'a/nested/someAction' 같이 네임스페이스 체인을 써야 합니다.
중첩 모듈은 구조가 너무 복잡해질 수 있으니,
정말 게층 구조가 필요한 도메인에서만 신중하게 도입하는 것이 좋습니다.
정리
아래와 같을 때 Vuex 모듈을 도입하는 것이 좋습니다.
- 스토어가 한 파일에 다 들어가기 버거워질 때
- 도메인(user, product, cart, ui ...)별로 로직을 나누고 싶을 때
- 같은 이름의 mutation/action을 여러 도메인에서 쓰고 싶을 때
- 재사용 가능한 스토어 조각을 만들고 싶을 때
=> 도메인별 Vuex 모듈 + namespaced: true 조합을 쓰면 코드가 훨씬 정리됩니다.
출처
'Framework > Vue' 카테고리의 다른 글
| Vue3 입문: Vite로 프로젝트 생성하고 createApp 큰 그림 잡기 (0) | 2025.11.24 |
|---|---|
| Vue CLI로 Vue 프로젝트 만들기 (0) | 2025.11.21 |
| Vuex를 이용한 Vue 전역 상태 관리 예제 (0) | 2025.11.20 |
| Vuex 기초: Vue 전역 상태 관리 (state, getters, mutations, actions, modules) (1) | 2025.11.20 |
| Vue Router와 Axios 활용해 라우팅 + API 연동으로 SPA 만들기 (0) | 2025.11.20 |