wabish / vuex-analysis

vuex 2.0 源码解读
MIT License
29 stars 1 forks source link

vuex 解读之 action #9

Open cobish opened 6 years ago

cobish commented 6 years ago

前言

mutation 用于同步更新 state,而 action 则是提交 mutation,并可进行异步操作,从而间接更新 state。

在还没解读之前,大家都认为 action 相对于 mutation 来说难理解得多,毕竟涉及到了异步。本篇就来看看 action 是如何处理异步的?

准备

解读之前,你可能需要对 promise 的相关知识有所了解:

  1. Promise.resolve()
  2. Promise.all()

解读

在解读前我们要熟悉 action 的用法,这里就直接解读,不再列出 action 的使用方式了。

构造函数 constructor 看起,只看与 action 相关的代码:

constructor (options = {}) {
  this._actions = Object.create(null)

  // bind commit and dispatch to self
  const store = this
  const { dispatch } = this
  this.dispatch = function boundDispatch (type, payload) {
    return dispatch.call(store, type, payload)
  }

  installModule(this, state, [], options)
}

action 的注册

installModule 里实现 action 的注册,然后就是 dispatch 的实现。定位到 installModule 方法。

function installModule (store, rootState, path, module, hot) {
  const {
    actions
  } = module

  if (actions) {
    Object.keys(actions).forEach(key => {
      registerAction(store, key, actions[key], path)
    })
  }
}

代码里循环 options 的 actions,将其作为参数传入 registerAction 方法,定位到 registerAction 方法中(自动忽略 path):

function registerAction (store, type, handler, path = []) {
  const entry = store._actions[type] || (store._actions[type] = [])
  const { dispatch, commit } = store
  entry.push(function wrappedActionHandler (payload, cb) {
    let res = handler({
      dispatch,
      commit,
      getters: store.getters,
      state: getNestedState(store.state, path),
      rootState: store.state
    }, payload, cb)
    if (!isPromise(res)) {
      res = Promise.resolve(res)
    }

    return res
  })
}

先找出里面需用到的 isPromise 方法,它的功能主要就判断是否有 then 函数:

export function isPromise (val) {
  return val && typeof val.then === 'function'
}

与 mutation 类似,注册时将 action 保存到了 store._actions 数组里面,为了使用 modules 的情况下 commit 时能触发相同名称的 action。

action 的注册比 mutation 多了一步,那就是将函数做一些处理,并将值返回。那到底做了什么处理呢?它会先使用 isPromise 方法判断,如果否,再使用 Promise.resolve() 进行处理。为什么要这样处理呢,主要是为了能够一个 action 执行完能够使用 then 继续处理下一个异步操作

上面的话有点难懂。如果 action 返回的是 Promise 对象比较好理解,因为能够使用 then 继续下一个操作。如果 action 没返回或者返回的不是 Promise 对象,那它就没有 then 函数可以下一步操作,看下代码示例吧:

actions: {
  actionA ({ commit }) {
    commit('someMutation')
  }
}

return dispatch('actionA').then(() => {
  commit('someOtherMutation')
})

上面的 actionA 在 dispatch 后没返回,如果没有使用 Promise.resolve() 进行处理,那么执行到 then 时就会报错了,那么接下去的操作也就无法执行了。

dispatch 的实现

再来看看 dispatch 的实现:

dispatch (type, payload) {
  const entry = this._actions[type]

  return entry.length > 1
    ? Promise.all(entry.map(handler => handler(payload)))
    : entry[0](payload)
}

如果只有一个 action 的比较好理解,直接执行返回 promise 对象即可。但当同名的 action 不止一个,则会使用 Promise.all() 处理,当所有的 action 执行完毕后这个 dispatch 才算执行完毕,才会执行 then 函数。

总结

至此,action 的解读完毕。可以把 action 看成是 mutation 的升级版。

在注册 action 时,action 函数执行完毕后会将返回值作为一个 Promise 对象返回,以便可以让异步按顺序执行。

在调用 dispatch 时,一个 store.dispatch 在不同模块中可以触发多个 action 函数。在这种情况下,只有当所有触发函数完成后,返回的 Promise 才会执行。

qianlongo commented 6 years ago

请教一下楼主

function registerAction (store, type, handler, path = []) {
  const entry = store._actions[type] || (store._actions[type] = [])
  // 这里用解构的形式读取了store上的dispatch, commit函数,为什么不解构getters呢。而是下面要用读取的方式
  const { dispatch, commit } = store
  entry.push(function wrappedActionHandler (payload, cb) {
    let res = handler({
      dispatch,
      commit,
      getters: store.getters,
      state: getNestedState(store.state, path),
      rootState: store.state
    }, payload, cb)
    if (!isPromise(res)) {
      res = Promise.resolve(res)
    }

    return res
  })
}
cobish commented 6 years ago

@qianlongo

其实解构 getters 也可以,我是这么认为的。像 handler 里不仅有 store.getters,还有 store.state。

作者可能是想区分属性和方法吧。只把方法解构了。

最新的 vuex 代码,已经不是这样写的了。registerAction

qianlongo commented 6 years ago

@cobish 3q