Vuex
Vuex是一个专为Vue开发的状态管理库,它采用集中式存储的方式管理应用的所有组件的状态,解决了多组件数据通信的问题,使数据操作更加简洁
打个比方:
- Vue Components(Vue 组件):就是你写的页面、按钮这些能看见的东西,是用户交互的入口。
- Actions(动作):负责 “干活前的准备”,比如调用后端接口、处理异步操作(像请求数据)。组件通过
Dispatch(派发)
告诉它:“我要干这个事儿啦!” - Mutations(变更):专门负责修改数据,Actions 干完准备活,会用
Commit(提交)
告诉它:“该改数据啦!” - State(状态):就是存数据的地方,Mutations 通过
Mutate(变更)
来改这里的数据,数据一变,Vue 组件就能感知到,重新Render(渲染)
页面,更新内容。 - Backend API(后端接口):如果需要从服务器拿数据、存数据,Actions 会和它打交道,比如 “去后端取用户信息”。
- Devtools(调试工具):辅助开发的,能看到数据怎么变的、操作咋执行的,方便调试找问题。
简单说就是:组件说要干啥 → Actions 处理逻辑、调用接口 → 让 Mutations 改数据 → State 数据变了 → 组件跟着更新,一套流程帮你管好 Vue 里的数据和交互 ,Devtools 还能盯着流程找 bug 。
我的一点理解:
tate (类似存储全局变量的数据)
getters (提供用来获取state数据的方法)
actions (提供跟后台接口打交道的方法,并调用mutations提供的方法)
mutations (提供存储设置state数据的方法)
我理解action中的方法,类似事件,只是在vuex中注册,需要通过dispatch触发这个方法
mutation中的方法,类似事件,只是在vuex中注册,需要通过commit触发这个方法
ps:(官网上说的几个概念)
It is a self-contained app with the following parts:
The state, the source of truth that drives our app;
The view, a declarative mapping of the state;
The actions, the possible ways the state could change in reaction to user inputs from the view.
This is an simple representation of the concept of "one-way data flow":
However, the simplicity quickly breaks down when we have multiple components that share a common state:
Multiple views may depend on the same piece of state.
Actions from different views may need to mutate the same piece of state.
Vuex 的内脏由五部分组成:State、Getter、Mutation、Action 和 Module。
Vuex 的 mutations 和 actions 两段式设计 主要是为了 让数据流向更清晰、便于追踪和调试。用大白话解释就是:
为什么要有这种设计?
想象一个公司里,数据(state)就像是公司的资产,所有员工(组件)都能查看资产,但不能随便修改。
如果每个员工都能直接改资产数据,公司就乱套了(比如有人偷偷转走钱,却没人知道是谁干的)。
所以公司规定:
-
修改资产必须通过财务部门(mutations):
财务部门有严格的记录,每笔修改都要登记(比如谁改的、改了多少、为什么改)。
这样一来,所有修改都是透明的,出问题可以直接查记录。 -
但员工不能直接找财务部门:
如果员工想修改数据(比如申请一笔预算),必须先写申请(dispatch action),交给经理(actions)审批。
经理可能会先确认申请是否合理(比如查一下有没有足够的预算),然后再把申请转给财务部门执行。
对应到 Vuex:
- mutations 就是 “财务部门”,它是唯一能直接修改数据的地方,而且必须同步执行(就像财务操作必须马上记录)。
- actions 就是 “经理”,负责处理复杂的逻辑(比如异步操作、权限验证),然后再调用 mutations 修改数据。
- 组件 就是 “员工”,不能直接改数据,只能通过 actions 提交申请。
这么设计的好处:
-
便于调试:
如果数据出了问题,可以直接查看 mutations 记录,知道是谁在什么情况下修改了数据。
Vuex DevTools 甚至能实时追踪每个 mutation 的变化。 -
避免竞态条件:
同步的 mutations 保证了数据修改的顺序性,不会出现多个修改同时进行导致的数据冲突。 -
分离关注点:
- 组件只负责触发 actions(提申请),不用关心具体怎么修改数据。
- actions 只负责处理逻辑(审批申请),不用关心数据怎么保存。
- mutations 只负责修改数据(执行财务操作),不用关心数据从哪里来。
举个栗子:
比如用户登录时:
- 组件触发
login
action(员工提交 “登录申请”)。 - action 先调用 API 验证用户信息(经理查征信),然后:
- 如果成功,调用
SET_USER
mutation 保存用户数据(通知财务打钱)。 - 如果失败,显示错误信息(拒绝申请)。
- 如果成功,调用
- mutation 修改 state(财务更新账户余额)。
这样整个流程清晰可控,代码也更容易维护。如果没有 actions,组件就得自己处理 API 请求和错误,代码会变得混乱且难以复用。
基本用法:
在项目中使用Vuex时,通常的做法是先在项目中创建一个store模块,然后导入并挂载store模块。store模块是用于管理状态数据的仓库。
编写store模块,创建src\store\index.js文件。
例子:
// store/index.js
import { createStore } from 'vuex'
import createPersistedState from 'vuex-persistedstate'
// 状态定义
const state = {
count: 5, // 合并原 reactive 的 count
user: {
id: '',
token: '',
isAuth: false
},
isLogin: false
}
// 突变(修改状态的唯一方式)
const mutations = {
// 计数器相关
SET_COUNT(state, value) {
state.count = value
},
INCREMENT(state) {
state.count++
},
// 用户认证相关
SET_LOGIN_STATUS(state, flag) {
state.isLogin = flag
state.user.isAuth = flag
},
SET_USER_INFO(state, userData) {
state.user = { ...state.user, ...userData }
},
LOGOUT(state) {
state.user = { id: '', token: '', isAuth: false }
state.isLogin = false
}
}
// 动作(处理异步操作,提交突变)
const actions = {
setLoginStatus({ commit }, flag) {
commit('SET_LOGIN_STATUS', flag)
},
async login({ commit }, credentials) {
try {
// 模拟登录请求
const response = await fetch('/api/login', {
method: 'POST',
body: JSON.stringify(credentials)
})
const data = await response.json()
commit('SET_USER_INFO', {
id: data.userId,
token: data.token,
isAuth: true
})
commit('SET_LOGIN_STATUS', true)
return true
} catch (error) {
console.error('登录失败:', error)
return false
}
},
logout({ commit }) {
commit('LOGOUT')
}
}
// 计算属性(类似 Vue 的计算属性)
const getters = {
count: state => state.count,
isLoggedIn: state => state.isLogin,
userInfo: state => state.user
}
// 创建 store 并启用持久化插件 需要安装 vuex-persistedstate,这里暂时不启用
// export default createStore({
// state,
// mutations,
// actions,
// getters,
// plugins: [
// createPersistedState({
// key: 'vuex-store',
// storage: localStorage,
// paths: ['user', 'isLogin'] // 只持久化用户相关状态
// })
// ]
// })
设计 isLogin
和 user.isAuth
双状态的核心逻辑
将登录状态拆分为两个字段并非多余,而是基于 "身份验证(Authentication)" 与 "权限授权(Authorization)" 的本质区别,在复杂业务中能显著提升系统灵活性。以下是具体优势:
1. 职责分离:解决不同维度的问题
状态字段 | 本质含义 | 典型场景 |
---|---|---|
isLogin | 是否登录(身份验证) | 控制用户能否进入系统(如:未登录则跳转登录页)。 |
user.isAuth | 是否有权限(授权) | 控制已登录用户能否操作特定功能(如:普通用户不能访问管理员后台)。 |
例子:
- 用户登录后,
isLogin = true
,但如果是普通用户访问管理员页面,user.isAuth = false
,系统会提示 "无权限"。 - 反之,若用户未登录,
isLogin = false
,无论user.isAuth
是什么状态,都无法进入系统。
2. 应对复杂权限场景:状态解耦更灵活
- 场景 1:登录状态正常,但权限过期
例如:用户登录后 token 有效期为 1 小时,1 小时后user.isAuth
变为false
,但isLogin
仍为true
(避免用户重复登录),系统可提示 "权限过期,请刷新页面"。 - 场景 2:多角色权限控制
同一个登录用户可能有不同权限(如:普通员工可查看数据,但不能修改),此时isLogin = true
,但user.isAuth
可细化为多个权限标记(如canEdit: false
)。
3. 代码可维护性:避免状态耦合引发的隐患
- 反例:合并为单一状态的风险
若将登录和权限合并为isValid
,当需要单独处理权限时(如权限刷新),必须修改isValid
,可能导致系统误判用户未登录(例如跳转到登录页),影响用户体验。 - 正例:双状态的清晰逻辑
// 登录页逻辑(只关心是否登录) if (!isLogin) { // 跳转登录 } // 管理员页面逻辑(既关心登录,也关心权限) if (!isLogin || !user.isAuth) { // 无权限提示 }
4. 符合认证体系的标准设计
在主流认证方案(如 OAuth 2.0、JWT)中:
- 身份验证(登录):通过用户名密码、Token 等确认用户身份,对应
isLogin
。 - 权限授权:通过角色、Scope 等控制操作权限,对应
user.isAuth
(或更细化的权限字段)。
这种拆分方式与行业标准一致,便于团队协作和后续功能扩展(如添加 RBAC 权限系统)。
双状态设计的核心价值
- 必要性:在简单项目中可能看似冗余,但在中大型项目(尤其是涉及权限分级、状态刷新、多角色管理)中,双状态是避免逻辑混乱的关键。
- 建议:若项目暂时无复杂权限需求,可暂时让
user.isAuth
与isLogin
保持同步,但保留字段以便后续扩展。
action 详解
实现逻辑:
1. setLoginStatus({ commit }, flag)
作用:直接设置登录状态(同步操作)
参数:
{ commit }
:解构自 context 对象,用于提交 mutationflag
:布尔值(true
表示已登录,false
表示未登录)
核心逻辑:
setLoginStatus({ commit }, flag) {
commit('SET_LOGIN_STATUS', flag) // 直接提交对应的 mutation
}
~:
这是一个简单的 "中转函数",组件如果想直接修改登录状态(比如测试环境下快速切换),可以调用这个 action。
但实际项目中,登录状态通常是通过登录 API 验证后自动设置的,所以这个 action 用得较少。
实际项目不可能这样的,这里只是为举例而举例。
这个 action 的核心是:不经过任何验证流程,直接修改登录状态。
比如:
// 组件中调用(危险操作!)
this.$store.dispatch('setLoginStatus', true)
这相当于在系统里直接把 "未登录" 改成 "已登录",跳过了账号密码验证、token 获取等关键流程。
2. 实际项目中,状态更新必须 "有依据"
在真实项目中,登录状态的变更必须满足以下条件:
- 必须通过后端验证(如账号密码正确、token 有效)
- 必须获取必要的用户信息(如用户 ID、权限范围)
而 setLoginStatus
只做了一件事:单纯修改布尔值,没有处理:
- 后端 API 请求
- token 的存储(如 localStorage)
- 用户权限的初始化
3. 正确的状态更新方式:通过登录流程触发
实际项目中,登录状态应该由 login
action 触发,它包含完整流程:
- 发送登录请求到后端
- 后端返回用户信息和 token
- 同时更新 Vuex 状态 + 本地存储(如 localStorage)
- 标记登录状态
// 正确做法:通过 login action 触发状态更新
async function handleLogin() {
const isSuccess = await this.$store.dispatch('login', {
username: this.form.username,
password: this.form.password
})
if (isSuccess) {
// 登录成功后的操作(如跳转页面)
}
}
2. async login({ commit }, credentials)
作用:处理登录流程(异步操作)
参数:
credentials
:用户输入的登录凭证(如{ username: 'test', password: '123' }
)
核心逻辑:
async login({ commit }, credentials) {
try {
// 1. 发送登录请求到后端 API
const response = await fetch('/api/login', {
method: 'POST',
body: JSON.stringify(credentials)
})
// 2. 解析响应数据
const data = await response.json()
// 3. 提交 mutations 更新状态
commit('SET_USER_INFO', {
id: data.userId,
token: data.token,
isAuth: true
})
commit('SET_LOGIN_STATUS', true)
return true // 返回成功标志
} catch (error) {
console.error('登录失败:', error)
return false // 返回失败标志
}
}
~:
这是登录功能的核心流程,就像用户在现实中刷卡进门:
- 刷卡验证:把用户输入的账号密码发给后端(
fetch
请求)。 - 检查门禁卡:如果后端返回成功(如 token 和用户信息),说明验证通过。
- 开门放行:
- 用
SET_USER_INFO
把用户信息(如 ID、token)存到 Vuex 里(相当于给用户发一张门禁卡)。 - 用
SET_LOGIN_STATUS
标记为已登录(相当于在系统里登记 "这个人可以进来")。
- 用
- 反馈结果:返回
true
告诉组件 "登录成功了"。
3. logout({ commit })
作用:处理退出登录流程
参数:无
核心逻辑:
logout({ commit }) {
commit('LOGOUT') // 提交 LOGOUT mutation
}
~:
用户要离开系统时,调用这个 action:
- 收走门禁卡:
LOGOUT
mutation 会清空用户信息(如 token)和登录状态(就像收回用户的门禁卡)。 - 登记离开:把
isLogin
和user.isAuth
都设为false
(相当于在系统里登记 "这个人已经走了")。
这三个 actions 的协作流程
-
登录流程:
组件 → 调用login
action → 发送请求到后端 → 验证成功后 → 提交两个 mutations(存用户信息 + 标记已登录)。 -
退出流程:
组件 → 调用logout
action → 提交LOGOUT
mutation → 清空用户信息和登录状态。 -
直接修改状态(较少用):
组件 → 调用setLoginStatus
action → 直接修改登录状态(不经过 API 验证)。
设计优势
-
分离关注点:
组件只需要触发 action(如login
),不用关心具体的登录逻辑和状态修改细节。
所有认证逻辑都集中在 actions 里,便于维护和复用。 -
异步处理:
用async/await
优雅地处理异步请求,避免回调地狱,代码更易读。 -
再啰嗦一句:async/await 就是 “异步操作的语法糖”
-
错误处理:
通过try/catch
捕获登录失败的情况,并返回布尔值给组件,方便组件根据结果做不同处理(如显示错误提示)。 -
可测试性:
每个 action 都可以单独测试(如模拟 API 响应,验证是否正确提交 mutations)。
Vuex 中用户认证相关 Mutations 详解
这段代码是 Vuex 中用于管理用户认证状态的核心 mutations,下面我们逐一拆解它们的作用和实现逻辑:
1. SET_LOGIN_STATUS(state, flag)
作用:设置用户的登录状态(登录 / 退出)
参数:
state
:Vuex 的状态对象(包含isLogin
和user
等数据)flag
:布尔值(true
表示已登录,false
表示未登录)
核心逻辑:
SET_LOGIN_STATUS(state, flag) {
state.isLogin = flag // 直接修改登录状态标记
state.user.isAuth = flag // 同时更新用户对象中的认证状态
}
~:
就像在系统里挂一个 "开门 / 关门" 的牌子:
- 当
flag
是true
,相当于挂 "营业中",isLogin
变为true
,同时告诉用户对象 "你已通过认证"(user.isAuth = true
)。 - 当
flag
是false
,相当于挂 "打烊了",两个状态一起置为false
。
2. SET_USER_INFO(state, userData)
作用:更新用户的详细信息(如 ID、Token、权限等)
参数:
state
:Vuex 状态对象userData
:包含用户信息的对象(如{ id: 123, token: 'abc', role: 'admin' }
)
核心逻辑:
SET_USER_INFO(state, userData) {
state.user = { ...state.user, ...userData } // 合并新旧用户数据
}
~:
比如用户刚登录时,后端返回了用户信息:
- 原来的
state.user
可能是空对象{}
,现在要填入数据。 { ...state.user, ...userData }
相当于把新旧数据摊开拼在一起:- 如果
userData
有新字段(如token
),就直接添加; - 如果有重复字段(如
isAuth
),就用userData
的值覆盖旧值。
- 如果
- 最终
state.user
会变成最新的用户信息,比如{ id: 123, token: 'abc', isAuth: true }
。
3. LOGOUT(state)
作用:用户退出登录,清空认证状态
参数:state
(Vuex 状态对象)
核心逻辑:
LOGOUT(state) {
state.user = { id: '', token: '', isAuth: false } // 重置用户对象为初始值
state.isLogin = false // 标记为未登录
}
~:
相当于用户离开系统时的 "清场操作":
- 把用户对象里的敏感信息(ID、Token)全部清空,只保留初始的空字符串和
isAuth: false
; - 同时把全局的
isLogin
标记为false
,相当于告诉整个系统:"现在没有用户登录了"。
这三个 mutations 的协作逻辑
-
登录流程:
组件 → 触发login
action → action 调用后端接口 → 成功后调用SET_USER_INFO
(存用户数据)和SET_LOGIN_STATUS(true)
(标记已登录)。 -
退出流程:
组件 → 触发logout
action → action 直接调用LOGOUT
mutation → 一次性清空用户数据和登录状态。 -
数据更新:
当用户信息变更(如修改个人资料),通过SET_USER_INFO
只更新变化的字段,保留未修改的字段(如token
不会被清空)。
设计优势
- 单一职责:每个 mutation 只做一件事,代码更清晰。
- 可追踪性:所有状态修改都通过 mutation 记录,方便用 Vuex DevTools 调试。
- 安全性:避免组件直接修改 state,防止数据被意外篡改。
Vuex Getters :状态派生与缓存
Vuex 的 getters 就像是 Vue 组件中的 计算属性(computed),主要用于从 state 中派生出新的数据或简化状态访问。下面用大白话解释它们的作用、解决的问题和使用方式:
一、Getters 是什么?解决什么问题?
1. 简化复杂状态访问
假设你的 state 里有个嵌套很深的对象:
state = {
user: {
profile: {
name: '张三',
age: 25,
address: {
city: '北京',
street: '朝阳区'
}
}
}
}
每次在组件里访问 city
都要写 this.$store.state.user.profile.address.city
,很麻烦。
getters 可以简化这个过程:
const getters = {
userCity: state => state.user.profile.address.city
}
在组件里直接用 this.$store.getters.userCity
就能拿到值。
2. 状态派生(计算新值)
比如你有个购物车 state:
state = {
cart: [
{ id: 1, name: '手机', price: 5000, quantity: 1 },
{ id: 2, name: '耳机', price: 500, quantity: 2 }
]
}
如果你想计算购物车总价,每次都在组件里写循环计算很麻烦,而且重复。
getters 可以封装这个计算逻辑:
const getters = {
cartTotal: state => {
return state.cart.reduce((total, item) => total + (item.price * item.quantity), 0)
}
}
在组件里直接用 this.$store.getters.cartTotal
就能拿到总价,而且自动响应 state 的变化。
3. 缓存计算结果
和 Vue 的计算属性一样,getters 的结果会缓存,只有依赖的 state 变化时才会重新计算。
比如上面的 cartTotal
,只要购物车内容不变,多次访问都不会重新计算,提高性能。
二、如何使用 Getters?
1. 在 Vuex 中定义 Getters
const getters = {
// 1. 直接返回 state 中的值(简化访问)
count: state => state.count,
isLoggedIn: state => state.isLogin,
userInfo: state => state.user,
// 2. 派生新值(计算属性)
isAdmin: state => state.user.role === 'admin',
// 3. 接受参数(返回函数)
getUserById: (state) => (id) => {
return state.users.find(user => user.id === id)
}
}
export default createStore({
state,
getters, // 别忘了在 store 中注册 getters
// ...
})
2. 在组件中使用 Getters
有两种方式:
方式一:直接访问 $store.getters
export default {
computed: {
// 直接访问
count() {
return this.$store.getters.count
},
isAdmin() {
return this.$store.getters.isAdmin
}
}
}
方式二:使用 mapGetters
辅助函数(推荐)
3. 使用带参数的 Getters
// 定义
const getters = {
getUserById: (state) => (id) => {
return state.users.find(user => user.id === id)
}
}
// 使用
export default {
methods: {
fetchUser() {
const user = this.$store.getters.getUserById(123)
console.log(user)
}
}
}
三、Getters vs State vs 组件计算属性
类型 | 作用 | 示例 |
---|---|---|
state | 存储原始数据 | state.count = 5 |
getters | 从 state 派生数据,类似 “计算属性”,有缓存 | getters.doubleCount = state.count * 2 |
组件计算属性 | 从组件内的数据或 Vuex state/getters 派生,仅在组件内使用 | computed: { total() { return this.items.length } } |
四、总结:Getters 的主要作用
- 代码复用:避免在多个组件中重复写相同的状态访问逻辑。
- 简化代码:用更短的语法访问深层嵌套的 state。
- 性能优化:缓存计算结果,避免不必要的重复计算。
- 关注点分离:将状态派生逻辑集中在 getters 中,组件只负责使用结果。
vue+element UI 学习总结笔记(十七)_vuex在项目中的应用_vue elment getters user-优快云博客