Vuex简介与核心概念
Vuex是Vue的官方状态管理库,专为Vue.js应用程序设计的状态管理模式。Vuex 采用单仓库加多级模块的树状结构,层级固定,依赖 mutations,整体更重。
Vuex的核心概念:
- State: 存储应用状态数据
- Getter: 用于从state中派生出一些状态
- Mutation: 更改state中状态的唯一方法,必须是同步函数
- Action: 可以包含异步操作,通过提交mutation来更改状态
- Module: 将store分割成模块,每个模块拥有自己的state、mutation、action、getter
Vuex安装与配置
安装Vuex:
# Vue 3.x
npm install vuex@next --save
# Vue 2.x
npm install vuex --save
# 使用yarn
yarn add vuex@next # Vue 3.x
yarn add vuex # Vue 2.x
基本配置 (Vue 3.x):
javascript
// src/store/index.js
import { createStore } from 'vuex'
export default createStore({
state: {
count: 0
},
getters: {
doubleCount: state => state.count * 2
},
mutations: {
increment(state) {
state.count++
}
},
actions: {
increment({ commit }) {
commit('increment')
}
}
})
在main.js中注册:
javascript
// src/main.js
import { createApp } from 'vue'
import App from './App.vue'
import store from './store'
const app = createApp(App)
app.use(store)
app.mount('#app')
State、Getter、Mutation、Action、Module详解
State详解
单一状态树:
javascript
const store = createStore({
state: {
count: 0,
user: {
name: 'John Doe',
email: 'john@example.com'
},
products: []
}
})
在组件中访问State:
javascript
// 方式1: 直接访问
this.$store.state.count
// 方式2: 使用计算属性
computed: {
count() {
return this.$store.state.count
}
}
// 方式3: 使用辅助函数
import { mapState } from 'vuex'
computed: {
...mapState(['count']),
// 或者使用对象形式
...mapState({
counter: 'count', // 映射 this.counter 到 store.state.count
user: state => state.user // 使用函数
})
}
在Composition API中访问State:
javascript
import { computed } from 'vue'
import { useStore } from 'vuex'
export default {
setup() {
const store = useStore()
return {
count: computed(() => store.state.count),
user: computed(() => store.state.user)
}
}
}
Getter详解
基础Getter:
javascript
const store = createStore({
state: {
todos: [
{ id: 1, text: 'Learn Vuex', done: true },
{ id: 2, text: 'Learn Pinia', done: false }
]
},
getters: {
// 基础getter
doneTodos: state => {
return state.todos.filter(todo => todo.done)
},
// 带参数的getter - 返回函数
getTodoById: (state) => (id) => {
return state.todos.find(todo => todo.id === id)
},
// 使用其他getter
doneTodosCount: (state, getters) => {
return getters.doneTodos.length
}
}
})
在组件中使用Getter:
javascript
// 方式1: 直接访问
this.$store.getters.doneTodos
// 方式2: 使用计算属性
computed: {
doneTodos() {
return this.$store.getters.doneTodos
}
}
// 方式3: 使用辅助函数
import { mapGetters } from 'vuex'
computed: {
...mapGetters(['doneTodos', 'doneTodosCount']),
// 使用对象形式
...mapGetters({
done: 'doneTodos', // 映射 this.done 到 store.getters.doneTodos
todosById: 'getTodoById'
})
}
// 使用带参数的getter
methods: {
getTodo(id) {
return this.$store.getters.getTodoById(id)
}
}
Mutation详解
基础Mutation:
javascript
const store = createStore({
state: {
count: 0,
user: null
},
mutations: {
// 基础mutation
increment(state) {
state.count++
},
// 带参数的mutation - 载荷(payload)
incrementBy(state, payload) {
state.count += payload.amount
},
// 对象风格的提交方式
setUser(state, payload) {
state.user = payload.user
},
// 使用常量定义mutation类型
[MUTATION_TYPES.SET_USER](state, user) {
state.user = user
}
}
})
// 定义mutation类型常量
const MUTATION_TYPES = {
SET_USER: 'SET_USER',
INCREMENT: 'INCREMENT'
}
提交Mutation:
javascript
// 方式1: 对象风格提交
this.$store.commit({
type: 'incrementBy',
amount: 10
})
// 方式2: 直接提交
this.$store.commit('incrementBy', { amount: 10 })
// 方式3: 使用辅助函数
import { mapMutations } from 'vuex'
methods: {
...mapMutations([
'increment', // 映射 this.increment() 到 this.$store.commit('increment')
// 使用对象形式
incrementBy: 'incrementBy' // 映射 this.incrementBy(amount) 到 this.$store.commit('incrementBy', amount)
]),
// 或者直接定义
increment() {
this.$store.commit('increment')
}
}
Mutation必须遵循Vue的响应规则:
javascript
mutations: {
// ✅ 正确: 添加新属性
addItem(state, item) {
Vue.set(state.items, item.id, item)
// 或者
state.items = { ...state.items, [item.id]: item }
},
// ✅ 正确: 删除属性
removeItem(state, itemId) {
Vue.delete(state.items, itemId)
// 或者
const { [itemId]: removed, ...rest } = state.items
state.items = rest
}
}
Action详解
基础Action:
javascript
const store = createStore({
state: {
count: 0,
user: null,
isLoading: false
},
mutations: {
INCREMENT(state) {
state.count++
},
SET_USER(state, user) {
state.user = user
},
SET_LOADING(state, status) {
state.isLoading = status
}
},
actions: {
// 基础action
increment({ commit }) {
commit('INCREMENT')
},
// 异步action
async fetchUser({ commit }, userId) {
commit('SET_LOADING', true)
try {
const response = await api.getUser(userId)
commit('SET_USER', response.data)
} catch (error) {
console.error('Error fetching user:', error)
} finally {
commit('SET_LOADING', false)
}
},
// 组合多个mutation的action
complexAction({ commit, state, dispatch }, payload) {
// 可以访问state
if (state.user) {
// 提交mutation
commit('INCREMENT')
// 派发其他action
dispatch('anotherAction', payload)
}
},
// 返回Promise的action
asyncAction({ commit }) {
return new Promise((resolve, reject) => {
setTimeout(() => {
commit('INCREMENT')
resolve()
}, 1000)
})
}
}
})
在组件中派发Action:
javascript
// 方式1: 直接派发
this.$store.dispatch('increment')
this.$store.dispatch('fetchUser', userId)
// 方式2: 对象风格派发
this.$store.dispatch({
type: 'fetchUser',
userId: 123
})
// 方式3: 使用辅助函数
import { mapActions } from 'vuex'
methods: {
...mapActions([
'increment', // 映射 this.increment() 到 this.$store.dispatch('increment')
// 使用对象形式
fetchUser: 'fetchUser' // 映射 this.fetchUser(userId) 到 this.$store.dispatch('fetchUser', userId)
]),
// 或者直接定义
loadUser(userId) {
this.$store.dispatch('fetchUser', userId)
.then(() => {
console.log('User loaded')
})
}
}
Module详解
基础模块:
javascript
// modules/user.js
const user = {
namespaced: true, // 开启命名空间
state: () => ({
user: null,
token: null
}),
getters: {
isAuthenticated: state => !!state.token,
userName: state => state.user ? state.user.name : ''
},
mutations: {
SET_USER(state, user) {
state.user = user
},
SET_TOKEN(state, token) {
state.token = token
}
},
actions: {
login({ commit }, credentials) {
return api.login(credentials)
.then(response => {
commit('SET_USER', response.user)
commit('SET_TOKEN', response.token)
return response
})
},
logout({ commit }) {
commit('SET_USER', null)
commit('SET_TOKEN', null)
}
}
}
export default user
注册模块:
javascript
import { createStore } from 'vuex'
import user from './modules/user'
import products from './modules/products'
export default createStore({
state: {
// 根级别state
appVersion: '1.0.0'
},
modules: {
user,
products,
// 动态注册模块
dynamicModule: {
namespaced: true,
// ...模块定义
}
}
})
在命名空间模块中访问:
javascript
// 在组件中访问命名空间模块
this.$store.state.user.user // 访问user模块的state
this.$store.getters['user/isAuthenticated'] // 访问user模块的getter
this.$store.commit('user/SET_USER', userData) // 提交user模块的mutation
this.$store.dispatch('user/login', credentials) // 派发user模块的action
// 使用辅助函数
import { mapState, mapGetters, mapMutations, mapActions } from 'vuex'
computed: {
...mapState('user', ['user', 'token']), // 访问user模块的state
...mapGetters('user', ['isAuthenticated', 'userName']) // 访问user模块的getter
},
methods: {
...mapMutations('user', ['SET_USER', 'SET_TOKEN']), // 访问user模块的mutation
...mapActions('user', ['login', 'logout']) // 访问user模块的action
}
动态注册模块:
javascript
// 注册模块
store.registerModule('newModule', {
// 模块定义
})
// 注册嵌套模块
store.registerModule(['nested', 'newModule'], {
// 模块定义
})
// 检查模块是否已注册
store.hasModule('newModule')
// 注销模块
store.unregisterModule('newModule')
Vuex在Vue3中的使用
Composition API中使用Vuex:
javascript
import { computed, onMounted } from 'vue'
import { useStore } from 'vuex'
export default {
setup() {
const store = useStore()
// 访问state
const count = computed(() => store.state.count)
const user = computed(() => store.state.user.user)
// 访问getters
const doubleCount = computed(() => store.getters.doubleCount)
const isAuthenticated = computed(() => store.getters['user/isAuthenticated'])
// 提交mutations
const increment = () => store.commit('increment')
const setUser = (userData) => store.commit('user/SET_USER', userData)
// 派发actions
const fetchUser = (userId) => store.dispatch('user/fetchUser', userId)
// 生命周期钩子
onMounted(() => {
fetchUser(123)
})
return {
count,
user,
doubleCount,
isAuthenticated,
increment,
setUser,
fetchUser
}
}
}
创建可组合的Vuex函数:
javascript
// composables/useUser.js
import { computed } from 'vue'
import { useStore } from 'vuex'
export function useUser() {
const store = useStore()
const user = computed(() => store.state.user.user)
const token = computed(() => store.state.user.token)
const isAuthenticated = computed(() => store.getters['user/isAuthenticated'])
const login = (credentials) => store.dispatch('user/login', credentials)
const logout = () => store.dispatch('user/logout')
return {
user,
token,
isAuthenticated,
login,
logout
}
}
// 在组件中使用
import { useUser } from '@/composables/useUser'
export default {
setup() {
const { user, isAuthenticated, login, logout } = useUser()
const handleLogin = async () => {
try {
await login({ email: 'test@example.com', password: 'password' })
console.log('Login successful')
} catch (error) {
console.error('Login failed:', error)
}
}
return {
user,
isAuthenticated,
handleLogin,
logout
}
}
}
Vuex与组件通信
父子组件通信:
html
// 父组件
<template>
<div>
<h2>Parent Component</h2>
<p>Count: {{ count }}</p>
<child-component></child-component>
</div>
</template>
<script>
import { mapState } from 'vuex'
import ChildComponent from './ChildComponent.vue'
export default {
components: { ChildComponent },
computed: {
...mapState(['count'])
}
}
</script>
// 子组件
<template>
<div>
<h3>Child Component</h3>
<p>Count: {{ count }}</p>
<button @click="increment">Increment</button>
</div>
</template>
<script>
import { mapState, mapMutations } from 'vuex'
export default {
computed: {
...mapState(['count'])
},
methods: {
...mapMutations(['increment'])
}
}
</script>
跨级组件通信:
html
// 无论组件层级多深,都可以通过Vuex共享状态
// 深层嵌套组件
<template>
<div>
<h4>Deeply Nested Component</h4>
<p>Global Theme: {{ theme }}</p>
<button @click="changeTheme('dark')">Dark Theme</button>
</div>
</template>
<script>
import { mapState, mapActions } from 'vuex'
export default {
computed: {
...mapState('ui', ['theme'])
},
methods: {
...mapActions('ui', ['changeTheme'])
}
}
</script>
事件总线替代方案:
javascript
// 传统方式: 使用事件总线进行组件间通信
// 使用Vuex: 通过共享状态和actions实现通信
// 组件A - 触发操作
export default {
methods: {
performAction() {
// 派发action而不是发射事件
this.$store.dispatch('performAction', { data: 'some data' })
}
}
}
// 组件B - 响应操作
export default {
computed: {
operationResult() {
return this.$store.state.operationResult
}
},
watch: {
operationResult(newVal) {
// 响应操作结果
console.log('Operation result:', newVal)
}
}
}
辅助函数(mapState, mapGetters等)
mapState详解:
javascript
import { mapState } from 'vuex'
export default {
computed: {
// 数组形式 - 直接映射同名state
...mapState(['count', 'user']),
// 对象形式 - 自定义名称
...mapState({
// 键为组件中的计算属性名,值为state中的路径
counter: 'count', // 映射 this.counter 到 store.state.count
// 使用函数形式 - 可以访问组件实例(this)
userName(state) {
return state.user ? state.user.name : 'Guest'
},
// 使用箭头函数 - 不能访问this
userEmail: state => state.user ? state.user.email : ''
}),
// 结合命名空间
...mapState('user', {
userToken: 'token',
userInfo: state => state.user
})
}
}
mapGetters详解:
javascript
import { mapGetters } from 'vuex'
export default {
computed: {
// 数组形式 - 直接映射同名getter
...mapGetters(['doneTodos', 'doneTodosCount']),
// 对象形式 - 自定义名称
...mapGetters({
completedTodos: 'doneTodos', // 映射 this.completedTodos 到 store.getters.doneTodos
// 使用函数形式
userStatus: (getters) => {
return getters['user/isAuthenticated'] ? 'Logged In' : 'Logged Out'
}
}),
// 结合命名空间
...mapGetters('user', ['isAuthenticated', 'userName']),
...mapGetters('user', {
loggedIn: 'isAuthenticated',
name: 'userName'
})
}
}
mapMutations详解:
javascript
import { mapMutations } from 'vuex'
export default {
methods: {
// 数组形式 - 直接映射同名mutation
...mapMutations(['increment', 'decrement']),
// 对象形式 - 自定义名称
...mapMutations({
add: 'increment', // 映射 this.add() 到 this.$store.commit('increment')
// 使用函数形式 - 可以返回一个函数
incrementBy: (commit) => (amount) => {
commit('incrementBy', { amount })
}
}),
// 结合命名空间
...mapMutations('user', ['SET_USER', 'SET_TOKEN']),
...mapMutations('user', {
setUser: 'SET_USER',
setToken: 'SET_TOKEN'
})
}
}
mapActions详解:
javascript
import { mapActions } from 'vuex'
export default {
methods: {
// 数组形式 - 直接映射同名action
...mapActions(['fetchData', 'submitData']),
// 对象形式 - 自定义名称
...mapActions({
loadData: 'fetchData', // 映射 this.loadData() 到 this.$store.dispatch('fetchData')
// 使用函数形式 - 可以返回一个函数
saveData: (dispatch) => (data) => {
return dispatch('submitData', { data, timestamp: Date.now() })
}
}),
// 结合命名空间
...mapActions('user', ['login', 'logout']),
...mapActions('user', {
userLogin: 'login',
userLogout: 'logout'
})
}
}
createNamespacedHelpers辅助函数:
javascript
import { createNamespacedHelpers } from 'vuex'
const { mapState, mapActions } = createNamespacedHelpers('user')
export default {
computed: {
// 自动使用user命名空间
...mapState(['user', 'token'])
},
methods: {
// 自动使用user命名空间
...mapActions(['login', 'logout'])
}
}
Vuex模块化
模块化结构:
javascript
src/store/
├── index.js # 主store文件
├── modules/
│ ├── index.js # 导出所有模块
│ ├── user.js # 用户模块
│ ├── products.js # 产品模块
│ ├── cart.js # 购物车模块
│ └── ui.js # UI状态模块
└── plugins/
├── logger.js # 日志插件
└── persist.js # 持久化插件
Vuex高级特性
插件系统:
javascript
// plugins/logger.js
const logger = store => {
// 初始化
console.log('Initial state:', store.state)
// 订阅mutation
store.subscribe((mutation, state) => {
console.log(`Mutation: ${mutation.type}`, mutation.payload)
})
// 订阅action
store.subscribeAction({
before: (action, state) => {
console.log(`Action: ${action.type}`, action.payload)
},
after: (action, state) => {
console.log(`Action finished: ${action.type}`)
},
error: (action, state, error) => {
console.error(`Action error: ${action.type}`, error)
}
})
}
export default logger
严格模式:
javascript
const store = createStore({
// ...其他配置
strict: process.env.NODE_ENV !== 'production' // 只在开发环境启用
})
// 在严格模式下,状态变更只能通过mutation
// 以下代码在严格模式下会抛出错误
state.count++ // ❌ 错误:直接修改状态
// 必须通过mutation
commit('increment') // ✅ 正确
热重载:
javascript
// 在开发环境中启用热重载
if (process.env.NODE_ENV === 'development' && module.hot) {
module.hot.accept(['./modules/user', './modules/products'], () => {
// 获取新的模块
const newUserModule = require('./modules/user').default
const newProductsModule = require('./modules/products').default
// 热重载模块
store.hotUpdate({
modules: {
user: newUserModule,
products: newProductsModule
}
})
})
}
动态模块:
javascript
// 注册动态模块
store.registerModule('dynamicModule', {
namespaced: true,
state: () => ({
data: null
}),
mutations: {
SET_DATA(state, data) {
state.data = data
}
},
actions: {
fetchData({ commit }) {
// 获取数据逻辑
}
}
})
// 注册保留状态的模块
store.registerModule('persistentModule', moduleConfig, { preserveState: true })
// 注册嵌套动态模块
store.registerModule(['nested', 'deeplyNested'], moduleConfig)
// 检查模块是否存在
const moduleExists = store.hasModule('dynamicModule')
// 卸载动态模块
store.unregisterModule('dynamicModule')
表单处理:
javascript
// 使用v-model和Vuex
<input v-model="message">
computed: {
message: {
get() {
return this.$store.state.message
},
set(value) {
this.$store.commit('updateMessage', value)
}
}
}
// 或者使用辅助函数
computed: {
...mapState({
message: state => state.message
})
},
methods: {
...mapMutations({
updateMessage: 'updateMessage'
})
},
watch: {
message(newVal) {
this.updateMessage(newVal)
}
}
Vuex最佳实践
1. 模块化设计:
- 将应用状态划分为逻辑模块
- 每个模块负责一个特定领域的状态管理
- 使用命名空间避免命名冲突
2. 状态设计:
- 保持state的扁平化,避免嵌套过深
- 只在state中存储必要的数据,派生数据使用getters
- 使用常量定义mutation类型
javascript
// mutation-types.js
export const MUTATION_TYPES = {
SET_USER: 'SET_USER',
SET_TOKEN: 'SET_TOKEN',
CLEAR_USER: 'CLEAR_USER',
ADD_PRODUCT_TO_CART: 'ADD_PRODUCT_TO_CART',
REMOVE_PRODUCT_FROM_CART: 'REMOVE_PRODUCT_FROM_CART'
}
3. 异步处理:
- 所有异步操作放在actions中
- 使用async/await处理异步流程
- 在actions中进行错误处理
javascript
actions: {
async fetchProducts({ commit }, filters = {}) {
commit('SET_LOADING', true)
commit('SET_ERROR', null)
try {
const response = await api.getProducts(filters)
commit('SET_PRODUCTS', response.data)
return response.data
} catch (error) {
commit('SET_ERROR', error.message)
throw error
} finally {
commit('SET_LOADING', false)
}
}
}
4. 组件中使用Vuex:
- 优先使用辅助函数简化代码
- 避免在组件中直接修改state,使用mutations
- 对于大型组件,考虑使用mapState和mapGetters的对象形式
javascript
computed: {
// 使用对象形式,添加注释说明
...mapState({
user: state => state.user.user, // 当前用户信息
isLoading: state => state.ui.isLoading // 加载状态
}),
// 本地计算属性
fullName() {
return this.user ? `${this.user.firstName} ${this.user.lastName}` : ''
}
}
5. 性能优化:
- 使用懒加载注册模块
- 避免在getters中进行计算密集型操作
- 对于大型状态,考虑按需加载数据
javascript
// 懒加载模块
const loadAdminModule = () => {
if (!store.hasModule('admin')) {
import('./modules/admin').then(adminModule => {
store.registerModule('admin', adminModule.default)
})
}
}
Vuex与Pinia对比
| 特性 | Vuex | Pinia |
|---|---|---|
| API设计 | 基于选项式API | 基于组合式API |
| 状态修改 | 必须通过mutations | 直接修改state |
| TypeScript支持 | 需要额外配置 | 原生支持,类型推断更好 |
| 模块化 | 基于modules | 每个store是一个模块 |
| 调试工具 | Vue Devtools | Vue Devtools |
| 服务端渲染 | 支持 | 支持 |
| 代码分割 | 支持懒加载模块 | 更简单,直接动态导入 |
| 热重载 | 支持 | 更简单的热重载 |
| 学习曲线 | 较陡峭 | 更平缓,更直观 |
从Vuex迁移到Pinia的基本步骤:
- 安装Pinia:
npm install pinia - 创建Pinia实例并注册到Vue应用
- 将Vuex模块转换为Pinia stores
- 更新组件中的状态访问方式
- 移除mutations,将逻辑移到actions
- 更新插件和中间件