在上一篇笔记中:Vuex 4源码学习笔记 - Store 构造函数都干了什么(四)

我们通过查看Store 构造函数的源代码可以看到主要做了三件事情:

  • 初始化一些内部变量以外
  • 执行installModule来初始化module以及子module,
  • 执行resetStoreState函数通过Vue3的reactive使store的状态实现“响应式”)。

今天我们我们通过官方的购物车示例来查看Vuex内部的工作流程,示例代码在examples/composition/shopping-cart文件夹内

Vuex配置代码如下:

import { createStore, createLogger } from 'vuex'
import cart from './modules/cart'
import products from './modules/products'const debug = process.env.NODE_ENV !== 'production'export default createStore({modules: {cart,products},strict: debug,plugins: debug ? [createLogger()] : []
})

Vuex组件module中各模块state配置代码部分:

/**
cart.js
*/
const state = {items: [],checkoutStatus: null
}
/**
products.js
*/
const state = {all: []
}

页面加载成功后可以看到state和getters

state和getters都是按照配置中module path的规则来划分的

然后我们看在ProductList.vue组件中,通过store.dispatch方法来调用vuex中actions,其他不重要的代码省略了。

<script>
import { useStore } from 'vuex'export default {setup () {const store = useStore()//... store.dispatch('products/getAllProducts')//...}
}
</script>

这是products.js中的actions

const actions = {getAllProducts ({ commit }) {shop.getProducts(products => {commit('setProducts', products)})}
}

第一步:store.dispatch将走到Store 构造函数内的this.dispatch函数

this.dispatch重写了Store类的原型prototype上的dispatch方法,根据优先级规则,会优先找实例上的属性方法,再去找prototype原型上的属性方法。

/* 将dispatch与commit调用的this绑定为store对象本身*/
const store = this
const { dispatch, commit } = this
this.dispatch = function boundDispatch (type, payload) {return dispatch.call(store, type, payload)
}

第二步:dispatch.call将走到Store 的原型方法dispatch

dispatch (_type, _payload) {//...
}

在dispatch函数内:

  • 第一,首先通过unifyObjectStyle函数来抹平成统一化的参数,因为actions和mutation都支持两种参数方式调用
// check object-style dispatch
// 抹平数据格式,dispatch调用可能是以载荷形式分发,也可能是以对象形式分发
const {type,payload
} = unifyObjectStyle(_type, _payload)// 建立action对象
const action = { type, payload }

两种传递参数形式:

// 以载荷形式分发
store.dispatch('incrementAsync', {amount: 10
})// 以对象形式分发
store.dispatch({type: 'incrementAsync',amount: 10
})
  • 第二,然后在this._actions中通过type来找到对应的actions函数数组,然后通过if做了判断
// 在this._actions中寻找对应的action数组
const entry = this._actions[type]
if (!entry) {if (__DEV__) {console.error(`[vuex] unknown action type: ${type}`)}return
}
  • 第三,之后的try/catch内的代码主要是用来执行action订阅器数组中的函数,比如在logger.js的插件中,我们就通过store.subscribeAction方法来订阅了action,那么在action运行时,就会执行我们订阅器函数。action订阅的状态主要有三种,分别为:before(action执行之前)、after(action执行之后)、error(action执行失败)。
try {// 浅拷贝以防止迭代器失效,如果订阅者同步调用取消订阅this._actionSubscribers.slice() // shallow copy to prevent iterator invalidation if subscriber synchronously calls unsubscribe.filter(sub => sub.before).forEach(sub => sub.before(action, this.state))
} catch (e) {if (__DEV__) {console.warn(`[vuex] error in before action subscribers: `)console.error(e)}
}
  • 第四,然后就是运行actions函数,如果是多个actions使用Promise.all来处理,如果是单个就直接运行
const result = entry.length > 1? Promise.all(entry.map(handler => handler(payload))): entry[0](payload)
  • 第五,最后,action会返回一个promise,如果成功或者失败都会调用action订阅器,我们可以在插件中订阅它。
return new Promise((resolve, reject) => {result.then(res => {try {this._actionSubscribers.filter(sub => sub.after).forEach(sub => sub.after(action, this.state))} catch (e) {if (__DEV__) {console.warn(`[vuex] error in after action subscribers: `)console.error(e)}}resolve(res)}, error => {try {this._actionSubscribers.filter(sub => sub.error).forEach(sub => sub.error(action, this.state, error))} catch (e) {if (__DEV__) {console.warn(`[vuex] error in error action subscribers: `)console.error(e)}}reject(error)})
})
  • 第六步,在上面第四步调用的action函数为我们昨天看到的installModule函数中registerAction函数包装而成。

下面的wrappedActionHandler函数就是经过包装的函数,通过call设置handler函数内的this指向为store对象,然后传入一个带有当前模块的dispatch、commit、getters、state和根模块rootGetters、rootState的对象。

接着如果函数返回的不是Promise,用Promise.resolve进行了包装,因为上面的action函数需要promise调用。

function registerAction (store, type, handler, local) {// 取出对应type的actions-handler集合const entry = store._actions[type] || (store._actions[type] = [])// 存储新的封装过的action-handlerentry.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)// action需要支持promise进行链式调用,这里进行兼容处理if (!isPromise(res)) {res = Promise.resolve(res)}// 开发者工具if (store._devtoolHook) {return res.catch(err => {store._devtoolHook.emit('vuex:error', err)throw err})} else {return res}})
}
  • 第七步:上面的handler.callhandler就是getAllProducts函数,函数的参数就是我们上面的对象,所以可以通过解构出commit供使用,在这个action中我们调用shop.getProducts这个异步的操作来获取数据,在获取完成数据之后,通过commit来调用mutation来更改数据。
const actions = {getAllProducts ({ commit }) {shop.getProducts(products => {commit('setProducts', products)})}
}
  • 第八步:上面的commit调用mutation会走到products模块的commit方法中,因为它是一个子模块,主要通过type = namespace + type,将它的namespace和type进行拼接,拼接完成后调用store上的commit方法。
/*** make localized dispatch, commit, getters and state* if there is no namespace, just use root ones* 生成局部的dispatch, commit, getters 和 state* 如果没有命名空间,就使用根命名空间*/
function makeLocalContext (store, namespace, path) {const noNamespace = namespace === ''const local = {//...commit: noNamespace ? store.commit : (_type, _payload, _options) => {const args = unifyObjectStyle(_type, _payload, _options)const { payload, options } = argslet { type } = args // 这时type为:setProductsif (!options || !options.root) {type = namespace + type // 这时type为:products/setProductsif (__DEV__ && !store._mutations[type]) {console.error(`[vuex] unknown local mutation type: ${args.type}, global type: ${type}`)return}}store.commit(type, payload, options)}}//...return local
}

第九步:和action相同,commit方法同理

/* 将dispatch与commit调用的this绑定为store对象本身*/
const store = this
const { dispatch, commit } = this
//...
this.commit = function boundCommit (type, payload, options) {return commit.call(store, type, payload, options)
}

第十步:走到commit方法

commit (_type, _payload, _options) {// ...
}

第十一步:和action相同,先抹平数据

// check object-style commit
// 抹平数据格式,mutation调用可能是以载荷形式分发,也可能是以对象形式分发
const {type,payload,options
} = unifyObjectStyle(_type, _payload, _options)

第十二步:在this._mutations中找到mutation函数的数组,然后是if判断,然后通过this._withCommit来运行每个mutation,

// 建立mutation对象
const mutation = { type, payload }
// 根据type在this._mutations中寻找对应的mutation数组
const entry = this._mutations[type]
if (!entry) {if (__DEV__) {console.error(`[vuex] unknown mutation type: ${type}`)}return
}
this._withCommit(() => {entry.forEach(function commitIterator (handler) {handler(payload)})
})

_withCommit是一个代理方法,所有触发mutation的进行state修改的操作都经过它,由此来统一管理监控state状态的修改。实现代码如下。

_withCommit (fn) {// 保存之前的提交状态const committing = this._committing// 进行本次提交,若不设置为true,直接修改state,strict模式下,Vuex将会产生非法修改state的警告this._committing = true// 执行state的修改操作fn()// 修改完成,还原本次修改之前的状态this._committing = committing
}

第十三步:与action相同,mutation也有个订阅器,可以在插件中进行订阅

 this._subscribers.slice() // shallow copy to prevent iterator invalidation if subscriber synchronously calls unsubscribe.forEach(sub => sub(mutation, this.state))if (__DEV__ &&options && options.silent
) {console.warn(`[vuex] mutation type: ${type}. Silent option has been removed. ` +'Use the filter functionality in the vue-devtools')
}

第十四步:上面_withCommit中运行的函数就是我们包装后的wrappedMutationHandler函数,和action差不多,也是绑定this,然后传入state,和payload

function registerMutation (store, type, handler, local) {// 取出对应type的mutations-handler集合const entry = store._mutations[type] || (store._mutations[type] = [])// commit实际调用的不是我们传入的handler,而是经过封装的entry.push(function wrappedMutationHandler (payload) {// 调用handler并将state传入handler.call(store, local.state, payload)})
}

第十五步:上面的handler.call的handler就是下面的mutations中的setProducts函数,在setProducts函数中我们修改了state中的all字段的值。

// mutations
const mutations = {setProducts (state, products) {state.all = products},//...
}

到这里state的值其实已经被修改了,那么页面是如何做到自动变更的呢?

这就要找到Store构造函数中的resetStoreState函数调用

export function resetStoreState (store, state, hot) {//.../* 这里使用Vue3的reactive来将state实现为响应式对象 */store._state = reactive({data: state})//...
}

重点在这里,Vuex将state通过Vue3的reactive函数将数据设置为响应式数据,所以在mutation中我们修改了state,由于其为reactive的数据,所以依赖它的页面或者组件也会自动变更。

今天我们通过dispatch的调用,一步步的看到Vuex的内部工作流程,这也是看源码的最好的方式,只要捋清楚大概主流程后,再去看那些细枝末节就容易多了。

一起学习更多前端知识,微信搜索【小帅的编程笔记】,每天更新

Vuex 4源码学习笔记 - 通过dispatch一步步来掌握Vuex整个数据流(五)相关推荐

  1. Vuex 4源码学习笔记 - 通过Vuex源码学习E2E测试(十一)

    在上一篇笔记中:Vuex 4源码学习笔记 - 做好changelog更新日志很重要(十) 我们学到了通过conventional-changelog来生成项目的Changelog更新日志,通过更新日志 ...

  2. Vuex 4源码学习笔记 - Vuex是怎么与Vue结合?(三)

    在上一篇笔记中:Vuex源码学习笔记 - Vuex开发运行流程(二) 我们通过运行npm run dev命令来启动webpack,来开发Vuex,并在Vuex的createStore函数中添加了第一个 ...

  3. Vuex 4源码学习笔记 - 做好changelog更新日志很重要(十)

    在上一篇笔记中:Vuex是如何发布的(九) 我们看到了Vuex是通过npm run release命令来运行release.js这个发布脚本来进行发布,在其中有一个步骤是通过yarn changelo ...

  4. Java多线程之JUC包:Semaphore源码学习笔记

    若有不正之处请多多谅解,并欢迎批评指正. 请尊重作者劳动成果,转载请标明原文链接: http://www.cnblogs.com/go2sea/p/5625536.html Semaphore是JUC ...

  5. RocketMQ 源码学习笔记 Producer 是怎么将消息发送至 Broker 的?

    RocketMQ 源码学习笔记 Producer 是怎么将消息发送至 Broker 的? 文章目录 RocketMQ 源码学习笔记 Producer 是怎么将消息发送至 Broker 的? 前言 项目 ...

  6. jquery源码学习笔记三:jQuery工厂剖析

    jquery源码学习笔记二:jQuery工厂 jquery源码学习笔记一:总体结构 上两篇说过,query的核心是一个jQuery工厂.其代码如下 function( window, noGlobal ...

  7. 雷神FFMpeg源码学习笔记

    雷神FFMpeg源码学习笔记 文章目录 雷神FFMpeg源码学习笔记 读取编码并依据编码初始化内容结构 每一帧的视频解码处理 读取编码并依据编码初始化内容结构 在开始编解码视频的时候首先第一步需要注册 ...

  8. Apache log4j-1.2.17源码学习笔记

    (1)Apache log4j-1.2.17源码学习笔记 http://blog.csdn.net/zilong_zilong/article/details/78715500 (2)Apache l ...

  9. PHP Yac cache 源码学习笔记

    YAC 源码学习笔记 本文地址 http://blog.csdn.net/fanhengguang_php/article/details/54863955 config.m4 检测系统共享内存支持情 ...

最新文章

  1. 产业互联网受瞩目:互联网主战场从To C转向To B | 企鹅经济学
  2. 用python画雪花-python使用turtle库与random库绘制雪花
  3. 326. Power of Three
  4. linux指向日志服务器
  5. P4015 运输问题
  6. C++PrimerPlus学习——第十七章编程练习
  7. 48 FI配置-财务会计-固定资产-与总账集成-定义折旧范围到总账的过账方式
  8. PHP常用的缓存技术汇总
  9. 《剑指offer》面试题18——树的子结构(C++)
  10. 祝贺在龙芯平台上编译jogamp(gluegen/jogl)2.3.2通过,并运行成功
  11. 一种深度学习方法---迁移学习
  12. 如何把模糊照片变清晰把相片变高清修图#ps教程#ps学习视频
  13. OpenHarmony命令行工具hdc_std使用总结
  14. 服务器主板不装系统进不去BIOS,bios进不去怎么装系统
  15. fastadmin btn-ajax,FastAdmin 在线命令生成时出错的分析
  16. 【2023 · CANN训练营第一季】晟腾AI入门课(TensorFlow)第一章 晟腾AI基础知识介绍
  17. 电脑中的"倚天剑+屠龙刀"【推荐】
  18. 个人整理的AD/2000技巧
  19. 【django】HttpRequest对象的属性和路由补充
  20. 看历史涨知识!高速公路知多少?

热门文章

  1. 7-1 英文单词排序
  2. 联想ERP项目实施案例分析(1):背景
  3. VS2019 下载MFC组件
  4. 2021年安全员-B证(广西省-2021版)考试报名及安全员-B证(广西省-2021版)免费试题
  5. 计算机术语翻译在线,计算机术语翻译
  6. python解决鸡兔同笼问题解法_Python解决鸡兔同笼问题的方法
  7. HTML制作个人主页
  8. SaltStack之lamt架构状态文件(下)
  9. 【Docker 学习笔记】Docker架构及三要素
  10. WinRAR去弹窗广告教程-国内版