Vuex 源码漫游:commit
与 dispatch
的爱恨情仇
大家好,我是老码,今天咱们来聊聊 Vuex 源码中一对儿冤家,一对儿好兄弟,那就是 commit
和 dispatch
。它们俩都是 Vuex 的重要成员,负责触发状态变更,但方式却大相径庭。今天咱们就深入源码,看看它们是如何各司其职,又如何协同合作的。
Vuex 基础回顾
在深入源码之前,咱们先简单回顾一下 Vuex 的核心概念。Vuex 就像一个全局的数据仓库,负责管理应用的状态。它主要包含以下几个部分:
- State (状态):应用的数据源,可以理解为 Vue 组件的
data
。 - Mutations (变更):修改 State 的唯一途径,必须是同步函数。
- Actions (动作):可以包含任意异步操作,通过 commit 触发 Mutations 来变更 State。
- Getters (获取器):从 State 中派生出的状态,类似于 Vue 组件的
computed
属性。 - Modules (模块):将 Store 分割成模块,每个模块拥有自己的 State、Mutations、Actions 和 Getters。
commit
负责触发 mutations
,而 dispatch
负责触发 actions
。这就是它们最本质的区别。
commit
:同步世界的使者
咱们先来看看 commit
的实现。在 Vuex 的源码中,commit
主要负责以下几个步骤:
- 参数校验:确保传入的参数符合规范,例如 mutation 的类型必须是字符串。
- 查找 Mutation:根据传入的 mutation 类型,在 Store 中查找对应的 mutation 函数。
- 执行 Mutation:调用 mutation 函数,并将 State 和 payload 作为参数传入。
咱们直接上代码,看看 commit
的简化版实现(这里为了方便理解,做了简化,真实源码更复杂):
// 简化版 commit 实现
function commit(_type, _payload, _options) {
const { type, payload, options } = unifyObjectStyle(_type, _payload, _options)
const mutation = this._mutations[type];
if (!mutation) {
console.error(`[vuex] unknown mutation type: ${type}`)
return
}
mutation.forEach(handler => handler(this.state, payload));
}
// 辅助函数,用于统一对象风格的参数
function unifyObjectStyle (type, payload, options) {
if (isObject(type) && type.type) {
options = payload
payload = type
type = type.type
}
return { type, payload, options }
}
这段代码的核心在于从 this._mutations
中根据 type
找到对应的 mutation 函数,然后执行它。注意,这里使用了 forEach
,意味着一个 type 可以对应多个 mutation 函数,这在插件中很常见。
重点:
- 同步执行:
commit
触发的 mutation 函数是同步执行的,这意味着在 mutation 函数执行完毕之前,commit
不会返回。 - 直接修改 State:mutation 函数可以直接修改 State,这是 Vuex 强制的规则。
- 参数校验:Vuex 会对传入的参数进行校验,确保 mutation 的类型是字符串,payload 可以是任意类型。
- 错误处理:如果找不到对应的 mutation,Vuex 会抛出一个错误。
咱们用一个表格来总结一下 commit
的特点:
特性 | 描述 |
---|---|
执行方式 | 同步 |
修改状态 | 直接修改 State |
参数 | 接受 mutation 类型和 payload |
错误处理 | 如果找不到对应的 mutation,会抛出错误 |
使用场景 | 适用于简单的、同步的状态变更,例如更新计数器、设置用户名称等。 |
dispatch
:异步世界的探险家
接下来,咱们来看看 dispatch
的实现。dispatch
的主要职责是触发 actions
,而 actions
可以包含任意异步操作。dispatch
的实现也比 commit
复杂一些,因为它需要处理异步操作。
dispatch
的简化版实现如下:
// 简化版 dispatch 实现
function dispatch(_type, _payload) {
const { type, payload } = unifyObjectStyle(_type, _payload)
const action = this._actions[type];
if (!action) {
console.error(`[vuex] unknown action type: ${type}`)
return
}
try {
return action.length > 1
? Promise.all(action.map(handler => handler(this, payload)))
: action[0](this, payload)
} catch (e) {
console.log(e)
}
}
这段代码和 commit
类似,首先根据 type
找到对应的 action 函数,然后执行它。不同之处在于:
- 异步执行:
dispatch
触发的 action 函数可以是异步的,这意味着在 action 函数执行完毕之前,dispatch
可能会返回一个 Promise。 - 通过 commit 修改 State:action 函数不能直接修改 State,必须通过
commit
触发 mutation 来修改 State。 - 返回 Promise:
dispatch
通常会返回一个 Promise,以便在 action 函数执行完毕后进行处理。
重点:
- 异步操作:action 函数可以包含任意异步操作,例如发送 HTTP 请求、读取本地存储等。
- 通过 commit 修改 State:action 函数不能直接修改 State,必须通过
commit
触发 mutation 来修改 State。 - 返回 Promise:
dispatch
通常会返回一个 Promise,以便在 action 函数执行完毕后进行处理。 - 错误处理:如果 action 函数抛出一个错误,
dispatch
会捕获这个错误并进行处理。
咱们再用一个表格来总结一下 dispatch
的特点:
特性 | 描述 |
---|---|
执行方式 | 异步 |
修改状态 | 通过 commit 触发 mutation 来修改 State |
参数 | 接受 action 类型和 payload |
返回值 | 通常返回一个 Promise |
错误处理 | 如果 action 函数抛出一个错误,dispatch 会捕获这个错误并进行处理 |
使用场景 | 适用于包含异步操作的状态变更,例如从服务器获取数据、提交表单等。 |
commit
和 dispatch
的区别
现在,咱们来总结一下 commit
和 dispatch
的区别:
特性 | commit |
dispatch |
---|---|---|
执行方式 | 同步 | 异步 |
修改状态 | 直接修改 State | 通过 commit 触发 mutation 来修改 State |
适用场景 | 简单的、同步的状态变更 | 包含异步操作的状态变更 |
目的 | 提交一个 mutation,直接改变 state | 派发一个 action, 间接的改变 state |
使用范围 | mutation 中,可以直接访问 state | action 中,不可以访问 state, 可以提交 mutation |
结果 | 没有返回值 | 可以返回 Promise, 进行链式调用 |
例子说话
光说不练假把式,咱们来几个例子,加深理解:
例子 1:更新计数器
// State
const state = {
count: 0
}
// Mutations
const mutations = {
increment (state) {
state.count++
}
}
// Actions
const actions = {
increment (context) {
context.commit('increment')
}
}
// 组件中使用
methods: {
increment () {
this.$store.dispatch('increment')
}
}
在这个例子中,increment
action 通过 commit
触发 increment
mutation,从而更新 count
的值。
例子 2:异步获取用户数据
// State
const state = {
user: null
}
// Mutations
const mutations = {
setUser (state, user) {
state.user = user
}
}
// Actions
const actions = {
async fetchUser (context, userId) {
const response = await fetch(`/api/users/${userId}`)
const user = await response.json()
context.commit('setUser', user)
}
}
// 组件中使用
methods: {
async fetchUser () {
await this.$store.dispatch('fetchUser', 123)
console.log(this.$store.state.user) // 输出用户数据
}
}
在这个例子中,fetchUser
action 发送一个 HTTP 请求,获取用户数据,然后通过 commit
触发 setUser
mutation,从而更新 user
的值。由于 fetchUser
是一个异步函数,所以我们使用了 async/await
语法。
总结
commit
和 dispatch
是 Vuex 中非常重要的两个方法,它们分别负责触发 mutations
和 actions
,从而实现状态变更。commit
适用于简单的、同步的状态变更,而 dispatch
适用于包含异步操作的状态变更。理解它们的区别和用法,对于编写高效、可维护的 Vuex 应用至关重要。
希望今天的分享对大家有所帮助。下次再见!