Vuex源码分析(五)-- actions

Vuex源码分析(五)-- actions

  • 官方描述
  • 源码分析
    • 构造函数store
    • installModule
    • makeLocalContext
    • Store.dispatch()
  • 总结

官方描述

Action 类似于mutation,不同在于:

  • Action 提交的是 mutation,而不是直接变更状态。
  • Action 可以包含任意异步操作。

让我们来注册一个简单的 action:

const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    increment (state) {
      state.count++
    }
  },
  actions: {
  	// context = {dispatch, commit, getters, state, rootGetters, rootState}
    increment (context) {
      context.commit('increment')
    }
  }
})

从这里可以看出,context有6个属性分别是dispatch, commit, getters, state, rootGetters, rootState。你也许会觉得奇怪,为什么gettersstate有各自的root信息,而dispatchcommit没有?

因为:dispatch, commit 是方法,可以通过传递参数指向对应的root方法,即store.dispatch(type, payload, {root: true});而gettersstate是属性,不能传参,所以重新引入了rootGetters, rootState属性

源码分析

构造函数store

mutations一样,核心方法都在 installModule 方法中。

export class Store {
  constructor (options = {}) {
    ...
    // 生成state模型
    this._modules = new ModuleCollection(options)
    const state = this._modules.root.state

    // 处理[state, getters, mutations, actions]的核心方法
    installModule(this, state, [], this._modules.root)
    ...
  }
}

installModule

这段代码主要是将原始 actions 对象封装到store上

function installModule (store, rootState, path, module, hot) {
  const namespace = store._modules.getNamespace(path)

  // 生成本地local对象
  const local = module.context = makeLocalContext(store, namespace, path)

  // 关键代码:
  module.forEachAction((action, key) => {
    const type = action.root ? key : namespace + key
    const handler = action.handler || action
    registerAction(store, type, handler, local)
  })

  module.forEachChild((child, key) => {
    installModule(store, rootState, path.concat(key), child, hot)
  })
}

核心是 module.forEachAction ,接下来对其进行展开

从这里可以看出,我们写的actions方法,可以接受两个参数,第一个是context = {dispatch, commit, getters, state, rootGetters, rootState},第二个是额外自定义参数payload。并且this指向全局store

  module.forEachAction((action, key) => {
    const type = action.root ? key : namespace + key
    const handler = action.handler || action
    registerAction(store, type, handler, local)
  })
// 将其展开如下
Object.keys(originActions).forEach(key => {
  const type = originActions[key].root ? key : namespace + key
  const handler = originActions[key].handler || originActions[key]
  const entry = store._actions[type] || (store._actions[type] = [])
  entry.push(function wrappedActionHandler (payload) {
    let res = handler.call(store, {
      dispatch: local.dispatch,
      commit: local.commit,
      getters: local.getters,
      state: local.state,
      rootGetters: store.getters,
      rootState: store.state
    }, payload)
    // 将结果封装成Promise返回
    if (!isPromise(res)) {
      res = Promise.resolve(res)
    }
    return res
  })
}

从这可以看出,actions可以写方法,也能写对象,以下两种写法效果是一样的

{
  actions: {
    increment (context) {
      context.commit('increment')
    }
  }
}
{
  actions: {
    increment: {
      root: false,
      handler (context) {
        context.commit('increment')
      }
    }
  }
}

在返回res之前,会判断res是否为Promise,如果不是,则用Promise进行封装返回。

makeLocalContext

接下来分析makeLocalContext方法对actions的封装

function makeLocalContext (store, namespace, path) {
  const noNamespace = namespace === ''

  // 从这里可以看出,local.dispatch 分两种情况
  // 1. 如果不是子模块,使用全局的 dispatch
  // 2. 如果是子模块,若!options.root===true,则自动给dispatch的type加上前缀,即默认为本地dispatch,且不用自己写前缀
  // local.commit同理
  const local = {
    dispatch: noNamespace ? store.dispatch : (_type, _payload, _options) => {
      const args = unifyObjectStyle(_type, _payload, _options)
      const { payload, options } = args
      let { type } = args

      if (!options || !options.root) {
        type = namespace + type
      }

      return store.dispatch(type, payload)
    },

    commit: noNamespace ? store.commit : (_type, _payload, _options) => {
      ...
      if (!options || !options.root) {
        type = namespace + type
      }

      store.commit(type, payload, options)
    }
  }

  Object.defineProperties(local, {
    getters: { get: () => {} },
    state: { get: () => {} }
  })

  return local
}

Store.dispatch()

export class Store {
  constructor (options = {}) {
    const { dispatch, commit } = this
    this.dispatch = function boundDispatch (type, payload) {
      return dispatch.call(store, type, payload)
    }
  }
  dispatch(_type, _payload) {
    const { type, payload } = unifyObjectStyle(_type, _payload)
    const action = { type, payload }
    const entry = this._actions[type]
    // 执行action的before订阅
    this._actionSubscribers.slice() 
        .filter(sub => sub.before)
        .forEach(sub => sub.before(action, this.state))
    // 执行dispatch
    const result = entry.length > 1
      ? Promise.all(entry.map(handler => handler(payload)))
      : entry[0](payload)
    return new Promise((resolve, reject) => {
      result.then(res => {
        // 执行action的after订阅
        this._actionSubscribers
          .filter(sub => sub.after)
          .forEach(sub => sub.after(action, this.state))
        resolve(res)
      })
    })
  }
}

总结

  1. 构造Storeactions有两种写法
  2. 对于actions方法传入的参数context有6个属性,context = {dispatch, commit, getters, state, rootGetters, rootState}
  3. dispatch, commit默认是局部的,可以传入dispatch(type, {root: true})指向全局dispatch
  4. 可以通过store.subscribeAction(fn, options)进行订阅,在执行dispatch前后,执行指定方法。fn = {brfore:fn1,after:fn2},如果fn是一个方法,代表before

你可能感兴趣的:(Vuex,vue,源码)