解释 Vuex 源码中 `commit` 和 `dispatch` 方法的实现,以及它们在触发 `mutations` 和 `actions` 时的区别。

Vuex 源码漫游:commitdispatch 的爱恨情仇

大家好,我是老码,今天咱们来聊聊 Vuex 源码中一对儿冤家,一对儿好兄弟,那就是 commitdispatch。它们俩都是 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 主要负责以下几个步骤:

  1. 参数校验:确保传入的参数符合规范,例如 mutation 的类型必须是字符串。
  2. 查找 Mutation:根据传入的 mutation 类型,在 Store 中查找对应的 mutation 函数。
  3. 执行 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。
  • 返回 Promisedispatch 通常会返回一个 Promise,以便在 action 函数执行完毕后进行处理。

重点:

  • 异步操作:action 函数可以包含任意异步操作,例如发送 HTTP 请求、读取本地存储等。
  • 通过 commit 修改 State:action 函数不能直接修改 State,必须通过 commit 触发 mutation 来修改 State。
  • 返回 Promisedispatch 通常会返回一个 Promise,以便在 action 函数执行完毕后进行处理。
  • 错误处理:如果 action 函数抛出一个错误,dispatch 会捕获这个错误并进行处理。

咱们再用一个表格来总结一下 dispatch 的特点:

特性 描述
执行方式 异步
修改状态 通过 commit 触发 mutation 来修改 State
参数 接受 action 类型和 payload
返回值 通常返回一个 Promise
错误处理 如果 action 函数抛出一个错误,dispatch 会捕获这个错误并进行处理
使用场景 适用于包含异步操作的状态变更,例如从服务器获取数据、提交表单等。

commitdispatch 的区别

现在,咱们来总结一下 commitdispatch 的区别:

特性 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 语法。

总结

commitdispatch 是 Vuex 中非常重要的两个方法,它们分别负责触发 mutationsactions,从而实现状态变更。commit 适用于简单的、同步的状态变更,而 dispatch 适用于包含异步操作的状态变更。理解它们的区别和用法,对于编写高效、可维护的 Vuex 应用至关重要。

希望今天的分享对大家有所帮助。下次再见!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注