Vuex
Vuex
是 Vue.js
应用的状态管理库,用于集中管理所有组件的状态【数据】。是专门在Vue
中实现集中管理共享数据的vue
插件,适用于任意组件间的通信,尤其是跨组件通信,如果只依赖于全局事件总线,代码就会很冗余,且在大型项目中难以维护。那么什么时候使用Vuex
呢,官网说法如下:
VueX
的核心流程总结如下:
State
:存储应用状态 ,即this.$store.state.xxx
。Mutation
:通过 commit
修改状态 ,即this.$store.commit('mutationName')
。Action
:通过 dispatch
触发异步操作 ,即this.$store.dispatch('actionName')
。Getter
:通过计算属性获取派生状态 ,即this.$store.getters.xxx
。Vuex
的环境搭建需要注意的是,vue2
只能使用vuex 3.x
的版本,vue3
只能使用vuex 4.x
的版本,对于Vue2
项目,安装指令如下:
npm install vuex3@3
在 Vue CLI
项目中创建 src/store
文件夹并在其中编写 index.js
,该结构Vuex
的推荐项目结构,主要目的是为了代码的可维护性、模块化和遵循最佳实践。src/store
是Vuex
状态管理的专用目录,核心作用包括:
集中管理状态:所有全局状态state
、操作mutations/actions
和派生状态getters
集中在此目录,避免代码分散。
模块化开发:方便将大型应用的状态拆分为多个模块modules
,每个模块对应一个文件,如 user.js
、cart.js
。
// 典型的项目结构
src/
├── store/
│ ├── index.js # Store 入口,整合模块和全局配置
│ ├── actions.js # 全局 actions(可选)
│ ├── mutations.js # 全局 mutations(可选)
│ └── modules/
│ ├── user.js # 用户模块
│ └── cart.js # 购物车模块
├── components/ # 组件
├── router/ # 路由
└── main.js # 应用入口
store/index.js
是 Vuex Store
的入口文件,主要职责是:
创建并导出Vuex Store
实例:在此文件中初始化 Vuex
的 state
、mutations
、actions
、getters
,并导出 store
实例供 Vue
应用使用。
// store/index.js
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
export default new Vuex.Store({
// 用于存放数据
state: { count: 0 },
// 用于数据处理
mutations: { increment(state) { state.count++ } },
// 用于响应组件中的动作
actions: { increment({ commit }) { commit('increment') } },
getters: { doubleCount: state => state.count * 2 }
});
整合模块:如果项目使用模块化,index.js
负责导入所有模块并合并到 Store
中:
// store/index.js
import user from './modules/user';
import cart from './modules/cart';
export default new Vuex.Store({
modules: { user, cart }
});
Vuex
的基本使用首先搭建一个最简单的vue CLI
项目,项目结构如下:
src/
├── store/
│ └── index.js # Vuex 主文件
├── components/
│ └── Counter.vue # 使用 Vuex 的组件
└── main.js # Vue 入口文件
创建Store
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
export default new Vuex.Store({
state: {
count: 0,
message: 'Hello Vuex'
},
mutations: {
// 同步修改 state
INCREMENT(state) {
state.count++;
},
SET_MESSAGE(state, newMessage) {
state.message = newMessage;
}
},
actions: {
// 异步操作
incrementAsync({ commit }) {
setTimeout(() => {
commit('INCREMENT');
}, 1000);
},
updateMessage({ commit }, newMessage) {
commit('SET_MESSAGE', newMessage);
}
},
getters: {
reversedMessage: state => {
return state.message.split('').reverse().join('');
}
}
});
main.js
挂载Store
import Vue from 'vue';
import App from './App.vue';
import store from './store';
new Vue({
store, // 挂载到根实例
render: h => h(App)
}).$mount('#app');
Counter.vue
组件中使用
<template>
<div>
<p>Count: {{ count }}</p>
<p>Message: {{ message }}</p>
<p>Reversed Message: {{ reversedMessage }}</p>
<button @click="increment">+1 (Mutation)</button>
<button @click="incrementAsync">+1 Async (Action)</button>
<input v-model="inputMessage" @change="updateMessage">
</div>
</template>
<script>
export default {
data() {
return {
inputMessage: ''
};
},
computed: {
// 获取 state
count() {
return this.$store.state.count;
},
message() {
return this.$store.state.message;
},
// 使用 getter
reversedMessage() {
return this.$store.getters.reversedMessage;
}
},
methods: {
// 提交 mutation
increment() {
this.$store.commit('INCREMENT');
},
// 分发 action
incrementAsync() {
this.$store.dispatch('incrementAsync');
},
updateMessage() {
this.$store.dispatch('updateMessage', this.inputMessage);
}
}
};
</script>
mapState
与mapGetters
在 Vuex
中,mapState
和 mapGetters
是用于简化组件中访问 State
和 Getters
的辅助函数。它们通过映射关系将 Store
中的状态或计算属性直接绑定到组件的计算属性中,减少冗余代码。
mapState
的用法:用于将 Vuex Store
中的 state
映射为组件的计算属性。
import { mapState } from 'vuex';
export default {
computed: {
// 使用对象展开符将 mapState 返回的多个计算属性合并到 computed 中
...mapState({
count: state => state.count, // 箭头函数形式
message: 'message', // 字符串简写,等价于 state => state.message)
userInfo: state => state.user.info // 嵌套状态访问
})
}
};
// 数组简写,适合同名映射
computed: {
...mapState(['count', 'message', 'user'])
}
// 使用了模块化且模块启用了命名空间namespaced: true需指定模块路径
computed: {
...mapState('user', {
username: state => state.username
})
}
mapGetters
:用于将 Vuex Store
中的 getters
映射为组件的计算属性。
import { mapGetters } from 'vuex';
export default {
computed: {
...mapGetters({
reversedMessage: 'reversedMessage', // 映射 this.reversedMessage 为 this.$store.getters.reversedMessage
formattedDate: 'user/formattedBirthday' // 模块化命名空间下的 getter
})
}
};
// 数组简写
computed: {
...mapGetters(['reversedMessage', 'user/formattedBirthday'])
}
// 模块化命名空间处理
computed: {
...mapGetters('user', ['formattedBirthday']) // 等价于 this.$store.getters['user/formattedBirthday']
}
mapAction
与mapMutaions
在 Vuex
中,mapActions
和 mapMutations
是用于简化组件中调用 Actions
和 Mutations
的辅助函数。它们将 Vuex
中的方法映射到组件的 methods
中,减少重复代码。
mapActions
:用于将 Vuex
的 actions
映射为组件的方法,直接分发 action
。
import { mapActions } from 'vuex';
export default {
methods: {
// 对象语法(可重命名)
...mapActions({
fetchData: 'loadData', // 将 this.fetchData() 映射为 this.$store.dispatch('loadData')
submit: 'user/submitForm' // 模块化路径
}),
// 数组语法(同名映射)
...mapActions([
'loadData', // 将 this.loadData() 映射为 this.$store.dispatch('loadData')
'user/fetchProfile'
])
}
};
methods: {
...mapActions('cart', [
'addToCart' // 映射 this.addToCart() 为 this.$store.dispatch('cart/addToCart')
])
}
mapMutatios
:用于将 Vuex
的 mutations
映射为组件的方法,直接提交 mutation
。
import { mapMutations } from 'vuex';
export default {
methods: {
// 对象语法(可重命名)
...mapMutations({
add: 'INCREMENT', // 将 this.add() 映射为 this.$store.commit('INCREMENT')
setMsg: 'SET_MESSAGE'
}),
// 数组语法(同名映射)
...mapMutations([
'INCREMENT', // 将 this.INCREMENT() 映射为 this.$store.commit('INCREMENT')
'SET_MESSAGE'
])
}
};
methods: {
...mapMutations('user', {
updateName: 'SET_NAME' // 映射 this.updateName() 为 this.$store.commit('user/SET_NAME')
})
}
Vuex
模块化在 Vuex
中,模块化Modularization
是一种将大型应用的状态管理拆分为独立模块的机制,适用于复杂项目。那么为什么需要模块化:
state
、mutation
、action
的命名冲突。下面是模块化基础结构:
src/
└── store/
├── index.js # 主入口文件
├── modules/
│ ├── user.js # 用户模块
│ ├── product.js # 商品模块
│ └── cart.js # 购物车模块
└── getters.js # 全局 getters(可选)
store/modules/user.js
export default {
namespaced: true, // 开启命名空间
state: () => ({
id: null,
name: 'Guest',
isLoggedIn: false
}),
mutations: {
SET_USER(state, payload) {
state.id = payload.id;
state.name = payload.name;
state.isLoggedIn = true;
},
LOGOUT(state) {
state.id = null;
state.name = 'Guest';
state.isLoggedIn = false;
}
},
actions: {
async login({ commit }, credentials) {
const userData = await api.login(credentials);
commit('SET_USER', userData);
}
},
getters: {
username: state => state.name,
isAdmin: state => state.id === 1
}
};
主入口文件 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 Vuex.Store({
modules: {
user, // 注册用户模块
cart // 注册购物车模块
}
});
namespace: true
:开启模块的命名空间,使模块的 state
、mutations
、actions
、getters
与其他模块隔离。
模块的局部状态
state
:始终是模块的局部状态,即使不开启命名空间。
访问根节点状态:在模块的 getters
和 actions
中,可通过第三个参数访问根状态。
getters: {
fullName(state, getters, rootState) {
return `${state.name} (${rootState.appVersion})`;
}
}
直接访问【不推荐】
// 访问模块的 state
this.$store.state.user.name;
// 调用模块的 action
this.$store.dispatch('user/login', credentials);
// 调用模块的 mutation
this.$store.commit('user/LOGOUT');
使用辅助函数 + 命名空间
import { mapState, mapActions } from 'vuex';
export default {
computed: {
// 映射模块的 state
...mapState('user', ['name', 'isLoggedIn']),
// 映射模块的 getter
...mapGetters('user', ['isAdmin'])
},
methods: {
// 映射模块的 action
...mapActions('user', ['login']),
// 映射模块的 mutation
...mapMutations('user', ['LOGOUT'])
}
};
使用 createNamespacedHelpers
【推荐】
import { createNamespacedHelpers } from 'vuex';
const { mapState, mapActions } = createNamespacedHelpers('user');
export default {
computed: {
...mapState(['name', 'isLoggedIn']),
...mapGetters(['isAdmin'])
},
methods: {
...mapActions(['login']),
...mapMutations(['LOGOUT'])
}
};
访问根节点状态、方法:在模块的 actions
或 getters
中。
actions: {
fetchData({ commit, rootState, rootGetters }) {
// rootState 访问根状态
const appVersion = rootState.appVersion;
// rootGetters 访问根 getters
const isMobile = rootGetters.isMobile;
}
}
调用其他模块的action
:使用 { root: true }
参数。
actions: {
async checkout({ dispatch }) {
// 调用另一个模块的 action
await dispatch('cart/loadCartItems', null, { root: true });
}
}
注册模块
// 在组件或逻辑中动态添加模块
import userProfile from './modules/userProfile';
this.$store.registerModule('userProfile', userProfile);
卸载模块
this.$store.unregisterModule('userProfile');
通过下面案例,可以清晰理解Vuex
模块化在复杂项目中的应用方式,包括模块定义、通信机制和工程化实践。项目结构如下:
src/
├── store/
│ ├── index.js # Store 主入口
│ ├── modules/
│ │ ├── user.js # 用户模块
│ │ ├── cart.js # 购物车模块
│ │ └── product.js # 商品模块(动态注册)
│ └── types.js # Mutation/Action 类型常量
└── components/
└── App.vue # 主组件
完整代码如下:
类型常量:store/types.js
// Mutation 类型
export const SET_USER = 'SET_USER';
export const ADD_TO_CART = 'ADD_TO_CART';
// Action 类型
export const FETCH_USER = 'FETCH_USER';
用户模块:store/modules/user.js
import { FETCH_USER, SET_USER } from '../types';
export default {
namespaced: true,
state: () => ({
id: null,
name: 'Guest',
isLoggedIn: false
}),
mutations: {
[SET_USER](state, payload) {
state.id = payload.id;
state.name = payload.name;
state.isLoggedIn = true;
}
},
actions: {
async [FETCH_USER]({ commit }, userId) {
const response = await fetch(`/api/users/${userId}`);
const userData = await response.json();
commit(SET_USER, userData);
}
},
getters: {
username: state => state.name
}
};
购物车模块 store/modules/cart.js
import { ADD_TO_CART } from '../types';
export default {
namespaced: true,
state: () => ({
items: [],
total: 0
}),
mutations: {
[ADD_TO_CART](state, product) {
state.items.push(product);
state.total += product.price;
}
},
actions: {
// 调用商品模块的 action(假设商品模块已注册)
async addProduct({ commit, dispatch }, productId) {
const product = await dispatch(
'product/fetchProduct',
productId,
{ root: true }
);
commit(ADD_TO_CART, product);
}
}
};
商品模块:store/modules/product.js
export default {
namespaced: true,
state: () => ({
list: []
}),
actions: {
async fetchProduct({ commit }, productId) {
const response = await fetch(`/api/products/${productId}`);
return response.json();
}
}
};
Store
主入口 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 Vuex.Store({
modules: {
user,
cart
},
// 全局状态(可选)
state: {
appVersion: '1.0.0'
},
getters: {
appInfo: state => `App v${state.appVersion}`
}
});
组件中使用:components/App.vue
<template>
<div>
<!-- 用户模块 -->
<div v-if="isLoggedIn">
<p>Welcome, {{ username }}!</p>
<button @click="logout">Logout</button>
</div>
<div v-else>
<button @click="login(1)">Login as User 1</button>
</div>
<!-- 购物车模块 -->
<div>
<h3>Cart Total: ${{ cartTotal }}</h3>
<button @click="addToCart(101)">Add Product 101</button>
<ul>
<li v-for="item in cartItems" :key="item.id">{{ item.name }} - ${{ item.price }}</li>
</ul>
</div>
<!-- 全局状态 -->
<p>{{ appInfo }}</p>
</div>
</template>
<script>
import { mapState, mapActions, mapGetters } from 'vuex';
import { FETCH_USER, SET_USER } from '../store/types';
export default {
computed: {
// 用户模块
...mapState('user', ['isLoggedIn']),
...mapGetters('user', ['username']),
// 购物车模块
...mapState('cart', ['items', 'total']),
cartItems: state => state.cart.items,
cartTotal: state => state.cart.total,
// 全局状态
...mapGetters(['appInfo'])
},
methods: {
// 用户模块的 action 和 mutation
...mapActions('user', {
login: FETCH_USER
}),
...mapMutations('user', {
logout: SET_USER
}),
// 购物车模块的 action
...mapActions('cart', ['addProduct']),
// 封装商品添加逻辑
addToCart(productId) {
this.addProduct(productId);
}
},
created() {
// 动态注册商品模块
import('../store/modules/product').then(module => {
this.$store.registerModule('product', module.default);
});
},
beforeDestroy() {
// 卸载商品模块(按需)
this.$store.unregisterModule('product');
}
};
</script>