项目结构
推荐结构
基础结构
适用于中小型项目:
code
src/
├── store/
│ ├── index.js # store 入口文件
│ ├── state.js # 根状态
│ ├── mutations.js # 根 mutations
│ ├── actions.js # 根 actions
│ ├── getters.js # 根 getters
│ └── modules/ # 模块目录
│ ├── user.js # 用户模块
│ ├── cart.js # 购物车模块
│ └── products.js # 商品模块
├── App.vue
└── main.js标准结构
适用于中大型项目:
code
src/
├── store/
│ ├── index.js # store 入口文件,组装模块并导出 store
│ ├── mutation-types.js # Mutation 类型常量
│ ├── state.js # 根状态
│ ├── mutations.js # 根 mutations
│ ├── actions.js # 根 actions
│ ├── getters.js # 根 getters
│ ├── modules/ # 模块目录
│ │ ├── index.js # 模块统一导出
│ │ ├── user/ # 用户模块
│ │ │ ├── index.js # 模块入口
│ │ │ ├── state.js # 模块状态
│ │ │ ├── mutations.js # 模块 mutations
│ │ │ ├── actions.js # 模块 actions
│ │ │ └── getters.js # 模块 getters
│ │ ├── cart/ # 购物车模块
│ │ │ └── ...
│ │ └── products/ # 商品模块
│ │ └── ...
│ └── plugins/ # 插件目录
│ ├── persist.js # 持久化插件
│ └── logger.js # 日志插件
├── api/ # API 接口
│ ├── user.js
│ ├── cart.js
│ └── products.js
├── utils/ # 工具函数
├── App.vue
└── main.js大型项目结构
适用于企业级大型项目:
code
src/
├── store/
│ ├── index.js # store 入口
│ ├── mutation-types.js # Mutation 类型常量
│ ├── rootState.js # 根状态
│ ├── rootMutations.js # 根 mutations
│ ├── rootActions.js # 根 actions
│ ├── rootGetters.js # 根 getters
│ │
│ ├── modules/ # 业务模块
│ │ ├── index.js # 模块导出
│ │ │
│ │ ├── user/ # 用户模块
│ │ │ ├── index.js # 模块入口
│ │ │ ├── state.js
│ │ │ ├── mutations.js
│ │ │ ├── actions.js
│ │ │ ├── getters.js
│ │ │ └── types.js # 模块私有类型
│ │ │
│ │ ├── cart/ # 购物车模块
│ │ │ └── ...
│ │ │
│ │ ├── products/ # 商品模块
│ │ │ └── ...
│ │ │
│ │ └── common/ # 公共模块
│ │ ├── app.js # 应用状态
│ │ ├── error.js # 错误状态
│ │ └── loading.js # 加载状态
│ │
│ ├── plugins/ # 插件
│ │ ├── index.js # 插件导出
│ │ ├── persist.js # 持久化
│ │ ├── logger.js # 日志
│ │ └── error.js # 错误处理
│ │
│ └── utils/ # store 工具函数
│ ├── storage.js # 存储工具
│ └── helpers.js # 辅助函数
│
├── api/ # API 接口
│ ├── index.js # API 导出
│ ├── request.js # 请求封装
│ ├── user.js
│ ├── cart.js
│ └── products.js
│
├── types/ # TypeScript 类型定义
│ ├── user.d.ts
│ ├── cart.d.ts
│ └── products.d.ts
│
├── App.vue
└── main.js文件组织
入口文件 (index.js)
javascript
// store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
import * as actions from './actions'
import * as getters from './getters'
import state from './state'
import mutations from './mutations'
import modules from './modules'
import plugins from './plugins'
Vue.use(Vuex)
const store = new Vuex.Store({
state,
mutations,
actions,
getters,
modules,
plugins,
strict: process.env.NODE_ENV !== 'production'
})
export default store
// 热重载
if (module.hot) {
module.hot.accept(['./state', './mutations', './actions', './getters', './modules'], () => {
const newState = require('./state').default
const newMutations = require('./mutations').default
const newActions = require('./actions').default
const newGetters = require('./getters').default
const newModules = require('./modules').default
store.hotUpdate({
state: newState,
mutations: newMutations,
actions: newActions,
getters: newGetters,
modules: newModules
})
})
}Mutation 类型常量 (mutation-types.js)
javascript
// store/mutation-types.js
// 用户相关
export const SET_USER = 'SET_USER'
export const SET_TOKEN = 'SET_TOKEN'
export const CLEAR_USER = 'CLEAR_USER'
// 购物车相关
export const ADD_TO_CART = 'ADD_TO_CART'
export const REMOVE_FROM_CART = 'REMOVE_FROM_CART'
export const UPDATE_CART_QUANTITY = 'UPDATE_CART_QUANTITY'
export const CLEAR_CART = 'CLEAR_CART'
// 商品相关
export const SET_PRODUCTS = 'SET_PRODUCTS'
export const SET_CURRENT_PRODUCT = 'SET_CURRENT_PRODUCT'
// 应用状态
export const SET_LOADING = 'SET_LOADING'
export const SET_ERROR = 'SET_ERROR'State 文件
javascript
// store/state.js
export default {
// 应用全局状态
loading: false,
error: null,
// 用户信息
user: null,
token: null,
// 其他根状态
sidebarCollapsed: false,
theme: 'light'
}Mutations 文件
javascript
// store/mutations.js
import * as types from './mutation-types'
export default {
[types.SET_LOADING](state, loading) {
state.loading = loading
},
[types.SET_ERROR](state, error) {
state.error = error
},
[types.SET_USER](state, user) {
state.user = user
},
[types.SET_TOKEN](state, token) {
state.token = token
},
[types.CLEAR_USER](state) {
state.user = null
state.token = null
}
}Actions 文件
javascript
// store/actions.js
import * as types from './mutation-types'
import api from '@/api'
export default {
// 全局加载状态
setLoading({ commit }, loading) {
commit(types.SET_LOADING, loading)
},
// 全局错误处理
setError({ commit }, error) {
commit(types.SET_ERROR, error)
},
// 用户登录
async login({ commit, dispatch }, credentials) {
dispatch('setLoading', true)
try {
const response = await api.user.login(credentials)
commit(types.SET_TOKEN, response.token)
commit(types.SET_USER, response.user)
return response
} catch (error) {
dispatch('setError', error.message)
throw error
} finally {
dispatch('setLoading', false)
}
},
// 用户登出
async logout({ commit }) {
await api.user.logout()
commit(types.CLEAR_USER)
}
}Getters 文件
javascript
// store/getters.js
export default {
// 用户相关
isLoggedIn: state => !!state.token,
userName: state => state.user?.name || '未登录',
userAvatar: state => state.user?.avatar || '/default-avatar.png',
// 应用状态
isLoading: state => state.loading,
hasError: state => !!state.error,
errorMessage: state => state.error?.message || '',
// 主题
isDarkTheme: state => state.theme === 'dark'
}模块导出文件
javascript
// store/modules/index.js
import user from './user'
import cart from './cart'
import products from './products'
import common from './common'
export default {
user,
cart,
products,
common
}单个模块文件
javascript
// store/modules/user/index.js
import state from './state'
import mutations from './mutations'
import actions from './actions'
import getters from './getters'
export default {
namespaced: true,
state,
mutations,
actions,
getters
}javascript
// store/modules/user/state.js
export default {
userInfo: null,
token: localStorage.getItem('token') || null,
preferences: {
theme: 'light',
language: 'zh-CN'
}
}javascript
// store/modules/user/mutations.js
import { SET_USER, SET_TOKEN, CLEAR_USER } from '@/store/mutation-types'
export default {
[SET_USER](state, user) {
state.userInfo = user
},
[SET_TOKEN](state, token) {
state.token = token
if (token) {
localStorage.setItem('token', token)
} else {
localStorage.removeItem('token')
}
},
[CLEAR_USER](state) {
state.userInfo = null
state.token = null
localStorage.removeItem('token')
}
}插件组织
持久化插件
javascript
// store/plugins/persist.js
const STORAGE_KEY = 'vuex-store'
export default function createPersistPlugin(options = {}) {
const { key = STORAGE_KEY, paths = [] } = options
return store => {
// 初始化时从存储中恢复状态
const savedState = localStorage.getItem(key)
if (savedState) {
try {
const parsed = JSON.parse(savedState)
store.replaceState({
...store.state,
...parsed
})
} catch (e) {
console.error('Failed to parse persisted state:', e)
}
}
// 订阅状态变化,保存到存储
store.subscribe((mutation, state) => {
try {
let stateToPersist = state
// 如果指定了路径,只保存指定路径的状态
if (paths.length > 0) {
stateToPersist = paths.reduce((acc, path) => {
const keys = path.split('.')
let value = state
for (const key of keys) {
value = value[key]
}
acc[path] = value
return acc
}, {})
}
localStorage.setItem(key, JSON.stringify(stateToPersist))
} catch (e) {
console.error('Failed to persist state:', e)
}
})
}
}日志插件
javascript
// store/plugins/logger.js
export default function createLoggerPlugin(options = {}) {
const { collapsed = true, filter = () => true } = options
return store => {
store.subscribe((mutation, state) => {
if (!filter(mutation, state)) return
const groupMethod = collapsed ? console.groupCollapsed : console.group
groupMethod(`[Vuex] ${mutation.type}`)
console.log('Payload:', mutation.payload)
console.log('State:', state)
console.groupEnd()
})
}
}使用插件
javascript
// store/plugins/index.js
import createPersistPlugin from './persist'
import createLoggerPlugin from './logger'
const plugins = []
// 持久化插件
plugins.push(createPersistPlugin({
key: 'my-app-store',
paths: ['user.token', 'user.preferences']
}))
// 日志插件(仅开发环境)
if (process.env.NODE_ENV === 'development') {
plugins.push(createLoggerPlugin())
}
export default plugins命名规范
文件命名
| 类型 | 命名规范 | 示例 |
|---|---|---|
| 目录 | 小写,多个单词用连字符 | user-profile/ |
| 模块文件 | 小写 | user.js |
| 类型文件 | 小写,连字符 | mutation-types.js |
| 插件文件 | 小写 | persist.js |
Mutation 命名
javascript
// 使用大写蛇形命名
export const SET_USER = 'SET_USER'
export const ADD_TO_CART = 'ADD_TO_CART'
export const UPDATE_CART_QUANTITY = 'UPDATE_CART_QUANTITY'
// 动词 + 名词
SET_USER // 设置用户
ADD_ITEM // 添加项目
REMOVE_ITEM // 移除项目
UPDATE_DATA // 更新数据
RESET_STATE // 重置状态Action 命名
javascript
// 使用小驼峰命名
actions: {
fetchUser() {}, // 获取用户
fetchUserList() {}, // 获取用户列表
createUser() {}, // 创建用户
updateUser() {}, // 更新用户
deleteUser() {}, // 删除用户
login() {}, // 登录
logout() {} // 登出
}Getter 命名
javascript
// 使用小驼峰命名,可以是属性形式
getters: {
isLoggedIn() {}, // 是否已登录
userName() {}, // 用户名
cartTotal() {}, // 购物车总价
itemCount() {}, // 项目数量
filteredItems() {} // 过滤后的项目
}最佳实践
1. 按功能划分模块
javascript
// ✅ 推荐 - 按业务功能划分
modules/
├── user/ # 用户相关
├── cart/ # 购物车相关
├── products/ # 商品相关
└── orders/ # 订单相关
// ❌ 不推荐 - 按技术类型划分
modules/
├── states/
├── mutations/
├── actions/
└── getters/2. 统一导出模块
javascript
// store/modules/index.js
const modules = {}
const moduleFiles = require.context('.', true, /index\.js$/)
moduleFiles.keys().forEach(path => {
const moduleName = path.replace(/^\.\/(.*)\/index\.js$/, '$1')
if (moduleName !== 'index') {
modules[moduleName] = moduleFiles(path).default
}
})
export default modules3. 使用严格模式
javascript
// 仅在开发环境启用
const store = new Vuex.Store({
// ...
strict: process.env.NODE_ENV !== 'production'
})4. 合理使用根状态
javascript
// 根状态只存放全局共享的状态
state: {
loading: false, // 全局加载状态
error: null, // 全局错误
theme: 'light' // 全局主题
}
// 业务状态放在模块中
modules: {
user: { ... },
cart: { ... }
}5. API 与 Action 分离
javascript
// api/user.js - 纯 API 调用
export default {
login(credentials) {
return request.post('/auth/login', credentials)
},
logout() {
return request.post('/auth/logout')
},
getUserInfo() {
return request.get('/user/info')
}
}
// store/modules/user/actions.js - Action 处理业务逻辑
import api from '@/api/user'
export default {
async login({ commit }, credentials) {
const response = await api.login(credentials)
commit('SET_TOKEN', response.token)
commit('SET_USER', response.user)
return response
}
}6. 使用 TypeScript 增强
typescript
// types/store.d.ts
import { Store } from 'vuex'
interface User {
id: number
name: string
email: string
}
interface RootState {
loading: boolean
error: Error | null
}
interface UserState {
userInfo: User | null
token: string | null
}
declare module 'vue/types/vue' {
interface Vue {
$store: Store<RootState>
}
}常见问题
Q: 什么时候需要拆分模块?
当单个文件超过 200 行代码,或者多个组件共享同一类状态时,应该考虑拆分模块。
Q: 根状态和模块状态如何划分?
- 根状态:全局共享的状态,如 loading、error、theme
- 模块状态:特定业务领域的状态,如用户、购物车、商品
Q: 如何处理模块间的依赖?
javascript
// 通过 rootState 和 rootGetters 访问其他模块
actions: {
async checkout({ state, rootState, rootGetters }) {
const isLoggedIn = rootGetters['user/isLoggedIn']
if (!isLoggedIn) {
throw new Error('请先登录')
}
// ...
}
}Q: 如何实现状态的懒加载?
javascript
// 路由守卫中动态注册模块
router.beforeEach(async (to, from, next) => {
if (to.meta.requiresModule && !store.hasModule(to.meta.module)) {
const module = await import(`@/store/modules/${to.meta.module}`)
store.registerModule(to.meta.module, module.default)
}
next()
})