各位观众老爷,大家好!今天咱们来聊聊 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 的世界里玩得开心!