各位观众老爷,大家好!今天咱们来聊聊 Vuex 里两个非常重要的“大人物”—— commit
和 dispatch
。 它们在 Vuex 的世界里扮演着至关重要的角色,负责状态管理的“调度”工作。
咱们先来设想一个场景:你是一家餐厅的老板,而 Vuex 就是你的餐厅管理系统。state
就像你的食材仓库,mutations
就像厨房里的大厨,负责直接操作食材(修改 state),actions
就像服务员,负责接收顾客的订单(触发 actions),然后把订单交给大厨。commit
和 dispatch
呢? commit
就像厨房内部的指令传递,大厨之间互相协调; dispatch
就像服务员把顾客的订单传递到厨房。
commit
: 直接修改 State 的“快车道”
commit
负责同步地触发 mutations
。 换句话说,它会立即执行对应的 mutation,并直接修改 state。 这就像大厨拿到食材,立马加工,毫不犹豫。
咱们先来看看 Vuex 源码中 commit
的简化实现:
// 简化版的 commit 实现
function commit (_type, _payload, _options) {
const {
type,
payload,
options
} = unifyObjectStyle(_type, _payload, _options) // 统一参数格式
const mutation = this._mutations[type]; // 获取对应的 mutation
if (!mutation) {
console.error(`[vuex] unknown mutation type: ${type}`)
return
}
this._withCommit(() => { // 确保 mutation 在 Vuex 的上下文中执行
mutation.forEach(handler => handler(payload)) // 执行所有的 mutation handler
})
}
// 辅助函数:统一参数格式,支持 commit('type', payload) 和 commit({ type: 'type', payload: payload }) 两种调用方式
function unifyObjectStyle (type, payload, options) {
if (typeof type === 'object' && type.type) {
options = payload
payload = type
type = type.type
}
return { type, payload, options }
}
这里有几个关键点:
-
unifyObjectStyle
: 这个函数的作用是统一commit
的参数格式。 Vuex 允许你用两种方式调用commit
:commit('mutationName', payload)
commit({ type: 'mutationName', payload: payload })
unifyObjectStyle
函数会把这两种形式统一成{ type: 'mutationName', payload: payload }
这种格式,方便后续处理。 -
this._mutations[type]
: Vuex 会维护一个_mutations
对象,存储所有的 mutation。commit
通过type
找到对应的 mutation 函数。 类似于餐厅里大厨收到订单,订单上写着菜名,大厨根据菜名找到对应的烹饪方法。 -
this._withCommit
: 这个函数非常重要! 它确保 mutation 的执行是在 Vuex 的“监控”之下。 简单来说,它会设置一个标志位,告诉 Vuex: “现在正在执行 mutation,请注意状态变化,并通知所有订阅者”。 这就像餐厅老板在厨房里安装了监控摄像头,随时记录大厨的操作,确保食材没有被浪费。 -
mutation.forEach(handler => handler(payload))
: 一个type
可能会对应多个 mutation handler (虽然不常见)。 这里会遍历所有的 handler,并执行它们。 这就像一道菜可能需要多个大厨协同完成。
dispatch
: 异步操作的“中转站”
dispatch
负责触发 actions
。 actions
可以包含任意异步操作,比如发送 AJAX 请求、调用定时器等等。 dispatch
本身可以是同步的也可以是异步的,取决于 actions
函数内部的操作。
咱们再来看看 Vuex 源码中 dispatch
的简化实现:
// 简化版的 dispatch 实现
function dispatch (_type, _payload) {
const {
type,
payload
} = unifyObjectStyle(_type, _payload) // 统一参数格式
const action = this._actions[type]; // 获取对应的 action
if (!action) {
console.error(`[vuex] unknown action type: ${type}`)
return
}
try {
return action.length > 1
? Promise.all(action.map(handler => handler(payload)))
: action[0](payload)
} catch (e) {
console.error(e)
}
}
这里也有几个关键点:
-
unifyObjectStyle
: 和commit
一样,用于统一参数格式。 -
this._actions[type]
: Vuex 会维护一个_actions
对象,存储所有的 action。dispatch
通过type
找到对应的 action 函数。 这就像服务员根据顾客的订单,找到负责做这道菜的大厨。 -
action.length > 1 ? ... : ...
: 一个type
也可以对应多个 action handler。 如果有多个 handler,dispatch
会使用Promise.all
并行执行它们。 这就像顾客同时点了多道菜,厨房里多个大厨同时开工。 -
return action[0](payload)
或return Promise.all(...)
:dispatch
会返回 action 函数的返回值。 如果 action 函数返回一个 Promise,dispatch
也会返回这个 Promise, 允许你在组件中等待 action 完成。 这就像服务员把做好的菜端给顾客,顾客可以慢慢享用。
commit
vs dispatch
: 区别一览表
特性 | commit |
dispatch |
---|---|---|
作用 | 触发 mutation,直接修改 state | 触发 action,可以包含异步操作 |
执行方式 | 同步 | 可以是同步的,也可以是异步的 |
修改 state | 直接修改 | 间接修改,通过 commit mutation 修改 |
目的 | 响应同步事件,快速修改 state | 处理异步操作,最终提交 mutation 修改 state |
类似于餐厅 | 厨房内部指令传递,大厨之间互相协调 | 服务员把顾客的订单传递到厨房,可以等待上菜 |
为什么需要 dispatch
?
你可能会问:既然 commit
可以直接修改 state,为什么还需要 dispatch
这么一个“中间人”呢?
原因很简单: 为了处理异步操作。
想象一下,如果你的 mutation 里直接包含 AJAX 请求,会发生什么?
-
难以追踪状态变化: mutation 应该是同步的,这样才能方便地追踪状态变化。 如果 mutation 里包含异步操作,状态变化的时间点就变得不确定了。
-
难以进行调试: 如果 mutation 里出现错误,你很难确定错误是来自同步代码还是异步代码。
-
破坏了 Vuex 的单向数据流: 数据流应该是
Component -> Action -> Mutation -> State -> Component
。 如果直接在 mutation 里进行异步操作,就破坏了这个流程。
所以,Vuex 建议把所有的异步操作都放在 actions
里处理,然后通过 commit
提交 mutation 来修改 state。 这样可以保证数据流的清晰和可维护性。
举个栗子:
假设我们要实现一个用户登录的功能。
-
state
:state: { user: null, isLoading: false, error: null }
-
mutations
:mutations: { SET_USER (state, user) { state.user = user }, SET_LOADING (state, isLoading) { state.isLoading = isLoading }, SET_ERROR (state, error) { state.error = error } }
-
actions
:actions: { async login ({ commit }, credentials) { commit('SET_LOADING', true) // 开始加载 try { const response = await api.login(credentials) // 发送 AJAX 请求 const user = response.data commit('SET_USER', user) // 提交用户信息 commit('SET_LOADING', false) // 结束加载 } catch (error) { commit('SET_ERROR', error) // 提交错误信息 commit('SET_LOADING', false) // 结束加载 } } }
-
Component
:<template> <div> <button @click="login">Login</button> <div v-if="isLoading">Loading...</div> <div v-if="error">{{ error }}</div> <div v-if="user">Welcome, {{ user.name }}</div> </div> </template> <script> import { mapState, mapActions } from 'vuex' export default { computed: { ...mapState(['user', 'isLoading', 'error']) }, methods: { ...mapActions(['login']), login () { this.login({ username: 'test', password: 'password' }) } } } </script>
在这个例子中,login
action 负责发送 AJAX 请求,获取用户信息,然后通过 commit
提交 mutation 来修改 state。 组件通过 dispatch
触发 login
action,并根据 state 的变化来更新 UI。
总结:
commit
用于同步地触发mutations
,直接修改 state。 就像厨房内部指令传递,大厨之间互相协调,快速修改食材。dispatch
用于触发actions
,可以包含异步操作。 就像服务员把顾客的订单传递到厨房,可以等待上菜。actions
负责处理异步操作,然后通过commit
提交 mutation 来修改 state。
掌握了 commit
和 dispatch
的区别和用法,你就掌握了 Vuex 的核心。 以后再遇到状态管理的问题,就能游刃有余地解决了。
好了,今天的讲座就到这里。 感谢各位的观看! 希望大家以后在 Vuex 的世界里玩得开心!