VUEX 3.x源码分析——4. 理解Action

这是对vuex3.x版本的源码分析。
本次分析会按以下方法进行:

  1. 按官网的使用文档顺序,围绕着某一功能点进行分析。这样不仅能学习优秀的项目源码,更能加深对项目的某个功能是如何实现的理解。这个对自己的技能提升`,甚至面试时的回答都非常有帮助。
  2. 在围绕某个功能展开讲解时,所有不相干的内容都会暂时去掉,等后续涉及到对应的功能时再加上。这样最大的好处就是能循序渐进地学习,同时也不会被不相干的内容影响。省略的内容都会在代码中以…表示。
  3. 每段代码的开头都会说明它所在的文件目录,方便定位和查阅。如果一个函数内容有多个函数引用,这些都会放在同一个代码块中进行分析,不同路径的内容会在其头部加上所在的文件目录。

本章只讲解vuex中的Action,这也是vuex官网中“核心概念”的第四个。
想了解vuex中的其他源码分析,欢迎参考我发布的下列文章:
VUEX 3 源码分析——1. 理解State
VUEX 3 源码分析——2. 理解Getter
VUEX 3 源码分析——3. 理解Mutations
VUEX3 源码分析——4. 理解Action
VUEX 3 源码分析——5. 理解Module
VUEX 3 源码分析——6. 理解命名空间namespace
VUEX 3 源码分析——7. 模块的动态注册和卸载

官网示例

  • 先看一下官网示例:
  • 在actions上定义函数,函数接受一个context对象,这个对象可以调用commit,state和getters。(官方说context对象不是store实例本身,等会看内部的实现源码就能更直观地感受到了)
  • action通过dispatch方法触发
  • 辅助函数 mapActions
  • 如果你看过我之前分析的两篇文章(理解Getter理解Mutations),就能发现action的功能和getter,尤其是mutation大同小异,只是action能进行异步操作嘛。那JavaScript里面,哪种语法适合异步操作能——当然是Promise啦。
  • 所以通过深入分析开源项目中的特定功能,我们可以洞察到项目背后的统一设计思想。
  • 理解这一设计思想不仅有助于我们掌握单个功能的具体实现,还能帮助我们预测和理解其他功能的设计模式。
  • 此外,阅读和学习优秀的开源项目,能够显著提升我们的代码理解能力和审美水平,从而在软件开发实践中,更好地应用这些设计原则和模式。
const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    increment (state) {
      state.count++
    }
  },
  actions: {
    increment (context) {
      context.commit('increment')
    }
  }
})

store.dispatch('increment')

初始化Actions

  • 之前的Getter和Mutation初始化工作,都是在installModule函数中执行的,action也必然在此。
  • Store类初始化一个无原型链对象 this_actions:this._actions = Object.create(null)
  • 将用户定义的action按key-value格式,存储进 this._actions[key] 中,其中的每个key对应一个数组,数组中的元素为函数,每个函数都返回一个执行action的Promise
// store-util.js
export function installModule(store, rootState, path, module, hot) {
    ...
    module.forEachAction((action, key) => {
        const type = action.root ? key : namespace + key;
        const handler = action.handler || action;
        registerAction(store, type, handler, local)
    })
}

function registerAction(store, type, handler, local) {
    const entry = store._actions[type] || (store._actions[type] = []);
    entry.push(function wrappedActionHandler(payload) {
        const store_obj = {
            dispatch: local.dispatch,
            commit: local.commit,
            getters: local.getters,
            state: local.state,
            rootGetters: store.getters,
            rootSatte: store.state                      
        } // 这就是官方手册说的action函数接受的context对象不是store实例本身
        let res = handler.call(store, store_obj, payload)
        // 如果res不是promise,将被封装在一个Promise对象中
        if (!isPromise(res)) {
            res = Promise.resolve(res)        
        }
        return res
    })
}

// ./module/module.js
export default class Module {
    ...
    forEachAction(fn) {
        if (this._rawModule.actions) {
            forEachValue(this._rawModule.actions, fn)        
        }    
    }
}

实现dispatch方法

  • 在Store类上定义dispatch方法。将传入的参数格式化后,以Promise.all的方式调用 this._actions[type]中的所有promise,返回一个新的Promise。
  • 另外这里我想对第8行的相关代码(const result = entry.length > 1 的三元判断内容)做一点个人分析。
  • 当时看的时候,觉得这段代码很冗长。觉得 const result = Promise.all(entry.map(handler => handler(payload))) 这样写会不会更简洁明了,为什么非要对entry的不同长度来做不同处理。
  • 但是这样写更体现开源项目的设计思想。在一个元素的情况下(entry.length === 1),直接调用这个元素,会比启动一个all的调用更有效率。虽然在大多数情况下,这种额外开销都是无感知的。但是作为开源项目,是不清楚使用者的实际业务场景的,所以需要尽可能地进行全面考虑。如果有大量操作或高负载的情况下,这种性能优化就会很明显。
// ./store.js
export class Store {
    ...
    dispatch(_type, _payload) {
    	// unifyObjectStyle函数让 Actions 支持同样的载荷方式和对象方式进行分发
        const {type, payload} = unifyObjectStyle(_type, _payload)
        const action = {type, payload}    // 分发 sub action 用的
        ...
        const result = entry.length > 1
            ? Promise.all(entry.map(handler => handler(payload)))
            : entry[0](payload)        
        return new Promise((resolve, reject) => {
            result.then(res => {
                 ... // 插件的处理
                 resolve(res)           
            }, error => {
                ... // 插件的处理
                reject(error)
            })        
        })
    }
}

// ./store-util.js
export function unifyObjectStyle (type, payload, options) {
  if (isObject(type) && type.type) {
    options = payload
    payload = type
    type = type.type
  }
  return { type, payload, options }
}
  • 最后将dispatch方法以类似隔离的方式,绑定store为dispatch 的指定上下文。
  • 确保在不同的执行环境中调用dispatch时,它总是与创建它的store实例相关联。
// ./store.js
export class Store {
    const store = this;
    const { dispatch, commit } = this;
    this.dispatch = function boundDispatch(type, payload) {
        return dispatch.call(store, type, payload)    
    }
}

mapActions 辅助函数

  • 实现方式和mapMutations一模一样,只是将commit换成了dispatch
// ./helper.js
export const mapActions = normalizeNamespace((namespace, actions) => {
  const res = {}
  ...
  normalizeMap(actions).forEach(({ key, val }) => {
    res[key] = function mappedAction (...args) {
      // get dispatch function from store
      let dispatch = this.$store.dispatch
      ...
      return typeof val === 'function'
        ? val.apply(this, [dispatch].concat(args))
        : dispatch.apply(this.$store, [val].concat(args))
    }
  })
  return res
})

以上就是官网上Action的相关源码,下一遍会分析Module的实现源码。

  • 30
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值