剖析 Vuex 源码中 `dispatch` 方法的实现,它如何处理 `actions` 的异步性,并支持 Promise 返回。

咳咳,各位老铁,双击屏幕666!今天咱们来唠唠 Vuex 里那个风骚的 dispatch 方法,看看它怎么玩转异步操作,还能支持 Promise 返回,简直是前端界的扛把子。

开场白:dispatch 的江湖地位

在 Vuex 的世界里,dispatch 就像一个总指挥,它负责接收各种指令(也就是 actions),然后把这些指令分发给相应的执行者。 简单来说,你想对 Vuex 的 state 做点什么,不能直接动手,得先通过 dispatch 告诉它。

dispatch 的基本用法:同步与异步的抉择

首先,咱们先复习一下 dispatch 的基本用法。它可以用来触发同步的 actions,也可以用来触发异步的 actions

// store.js
const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    increment (state) {
      state.count++
    }
  },
  actions: {
    increment (context) {
      context.commit('increment')
    },
    async incrementAsync (context) {
      await new Promise(resolve => setTimeout(resolve, 1000))
      context.commit('increment')
    }
  }
})

// 组件中使用
store.dispatch('increment') // 同步
store.dispatch('incrementAsync') // 异步

上面的例子中,increment 是一个同步的 action,直接通过 context.commit 触发 mutationincrementAsync 是一个异步的 action,它使用 async/await 来模拟一个 1 秒的延迟,然后再触发 mutation

深入源码:dispatch 的内部构造

好,现在咱们要开始扒 dispatch 的源码了。 Vuex 的源码其实并不复杂,但是要理解它的设计思想,需要一点耐心。

// Vuex/src/store.js

// 重点关注这个 dispatch 方法
dispatch (_type, _payload) {
  // check object-style dispatch
  const {
    type,
    payload
  } = unifyObjectStyle(_type, _payload)

  const entry = this._actions[type]
  if (!entry) {
    if (process.env.NODE_ENV !== 'production') {
      console.error(`[vuex] unknown action type: ${type}`)
    }
    return
  }

  try {
    this._mutating = true // 标记正在mutating,防止在mutation过程中dispatch

    return entry.length > 1
      ? Promise.all(entry.map(handler => handler(payload)))
      : entry[0](payload)
  } finally {
    this._mutating = false // 恢复状态
  }
}

这段代码看起来有点抽象,咱们一步一步来解释。

  1. unifyObjectStyle:统一对象风格

    这个函数的作用是把两种不同的 dispatch 调用方式统一起来。 Vuex 支持两种 dispatch 的方式:

    • 字符串形式:store.dispatch('increment')
    • 对象形式:store.dispatch({ type: 'increment' })

    unifyObjectStyle 函数会把字符串形式的调用转换成对象形式,方便后续处理。

    // Vuex/src/utils.js
    export function unifyObjectStyle (type, payload, moduleNamespace) {
      if (typeof type === 'string') {
        return { type, payload }
      } else if (typeof type === 'object' && type.type) {
        return {
          type: type.type,
          payload: payload || type.payload
        }
      } else {
        throw new Error(`Expect action's type to be string or object with type attribute, but got ${typeof type}.`)
      }
    }

    简单来说,如果你的 dispatch 调用是 store.dispatch('increment'),那么 unifyObjectStyle 会把它变成 { type: 'increment', payload: undefined }

  2. this._actions[type]:查找对应的 action

    this._actions 是一个对象,它存储了所有注册的 actions。 每个 actiontype 作为 key,对应的 value 是一个 handler 函数或者一个 handler 函数的数组。

    如果找不到对应的 action,Vuex 会在开发环境下打印一个错误信息。

  3. entry.length > 1:处理多个 handler

    一个 action 可以对应多个 handler 函数。 这种情况通常发生在插件中,不同的插件可能需要对同一个 action 进行处理。

    如果一个 action 对应多个 handler 函数,Vuex 会使用 Promise.all 来并发执行这些 handler 函数。 这意味着所有的 handler 函数都会并行执行,只有当所有的 handler 函数都执行完毕后,dispatch 才会返回。

    如果只有一个 handler 函数,Vuex 会直接执行这个 handler 函数。

  4. handler(payload):执行 action

    这行代码是真正执行 action 的地方。 handler 函数接收一个 payload 参数,这个参数就是你在 dispatch 的时候传递的参数。

    handler 函数的返回值可以是任意值,也可以是一个 Promise 对象。 如果 handler 函数返回一个 Promise 对象,Vuex 会等待这个 Promise 对象 resolve 后再返回。

  5. try...finally:确保状态恢复

    this._mutating = truethis._mutating = false 这两行代码是为了防止在 mutation 过程中调用 dispatch。 如果在 mutation 过程中调用 dispatch,Vuex 会抛出一个错误。

    try...finally 语句确保无论 action 执行成功还是失败,this._mutating 的状态都会被恢复。

dispatch 如何处理异步性?

关键就在于 handler 函数的返回值。如果 handler 函数返回一个 Promise 对象,dispatch 会等待这个 Promise 对象 resolve 后再返回。

这使得我们可以使用 async/await 来编写异步的 actions,并且可以很方便地获取异步操作的结果。

// actions.js
const actions = {
  async fetchData (context) {
    const response = await fetch('/api/data')
    const data = await response.json()
    context.commit('setData', data)
    return data // 返回Promise resolve的值
  }
}

// 组件中使用
store.dispatch('fetchData').then(data => {
  console.log('数据加载完成:', data)
})

在上面的例子中,fetchData 是一个异步的 action,它使用 fetch API 来获取数据。 fetch API 返回一个 Promise 对象,await 关键字会等待这个 Promise 对象 resolve 后再执行后面的代码。

fetchData 函数返回获取到的数据,dispatch 方法会返回一个 Promise 对象,这个 Promise 对象会在 fetchData 函数 resolve 后 resolve,并且 resolve 的值就是 fetchData 函数的返回值。

dispatch 的 Promise 支持

dispatch 方法返回一个 Promise 对象,这意味着我们可以使用 thencatchfinally 等方法来处理异步操作的结果。

store.dispatch('fetchData')
  .then(data => {
    console.log('数据加载成功:', data)
  })
  .catch(error => {
    console.error('数据加载失败:', error)
  })
  .finally(() => {
    console.log('加载完成')
  })

dispatch 的多 handler 处理

前面提到过,一个 action 可以对应多个 handler 函数。 在这种情况下,dispatch 会使用 Promise.all 来并发执行这些 handler 函数。

// plugins/logger.js
const logger = store => {
  store.subscribeAction({
    before: (action, state) => {
      console.log(`[logger] action ${action.type} started`)
    },
    after: (action, state) => {
      console.log(`[logger] action ${action.type} finished`)
    }
  })
}

// store.js
const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    increment (state) {
      state.count++
    }
  },
  actions: {
    increment (context) {
      context.commit('increment')
    }
  },
  plugins: [logger]
})

// 组件中使用
store.dispatch('increment')

在上面的例子中,logger 插件订阅了所有的 actions,并在 action 执行前后打印日志。 当 dispatch('increment') 被调用时,increment actionlogger 插件的 beforeafter handler 都会被执行。 Vuex 会使用 Promise.all 来并发执行这些 handler 函数。

总结:dispatch 的核心价值

dispatch 方法是 Vuex 的核心之一,它负责接收指令、分发指令、处理异步操作,并且支持 Promise 返回。 它把同步和异步的操作都统一起来,使得我们可以很方便地管理 Vuex 的 state

特性 描述
统一对象风格 使用 unifyObjectStyle 函数,将字符串形式和对象形式的 dispatch 调用统一起来。
异步处理 如果 action handler 返回一个 Promisedispatch 会等待这个 Promise 完成。
handler 允许一个 action 对应多个 handler,并使用 Promise.all 并发执行这些 handler
状态保护 使用 try...finally 语句确保在 mutation 过程中不会调用 dispatch,防止状态污染。
Promise 支持 dispatch 方法返回一个 Promise 对象,允许使用 thencatchfinally 等方法处理异步操作的结果。

彩蛋:一些需要注意的地方

  • 不要在 mutation 过程中调用 dispatch。 Vuex 会抛出一个错误。
  • 尽量使用 async/await 来编写异步的 actions。 这样可以使代码更简洁、更易读。
  • 如果一个 action 对应多个 handler 函数,要注意这些 handler 函数之间的依赖关系。 因为它们是并发执行的。

好了,今天的讲座就到这里。希望大家对 Vuex 的 dispatch 方法有了更深入的了解。 记住,源码面前,了无秘密! 下次再见!

发表回复

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