通俗易懂Vuex源码导读3-Vuex官方文档对照说明
Vuex 的使用解析
回顾前面几章我们介绍的内容
- 第零章对Vuex的整体运行思路,重点变量进行了介绍。
- 第一章介绍了Vuex的安装过程。
- 上一章介绍了Vuex的初始化过程,在正式使用Vuex前做了哪些准备工作。
- 这一章,将对照Vuex的官方说明文档,逐一介绍示例代码背后的运行逻辑。
首先介绍的是state
- 唯一:是指整个Vuex数据存储是由Store这一个对象实例完成的。
- 状态:是指各个数据。
- 树:指 module 和 state 的树结构。
- 通过 Store 变量可以查看当前项目的数据存储情况(作者的用意,可能是将Vuex作为Vue项目的整个数据存储库,将所有数据内容都交由Vuex进行管理),可以通过Store观察整个项目的数据情况。
- 通过热重载功能,进行数据的回退,实现时间穿梭的调整功能(本系列文章没有介绍到,有兴趣的同学可以看看源码。hot关键字相关内容)。
在计算属性中使用state
不知你是否会疑惑,在Store的构造函数中并没有定义类的state属性,为什么可以通过store.state获取到state数据呢?- state的定义是通过class的取值函数getter及存值函数setter来完成的。取值设值函数的功能,和Object.defineProperties函数类似,相当于设置拦截器,当获取值或设置值时,调用对应函数。
在vue组件中,调用this.$store.state.count的运行逻辑是:
- this.$store在「mixin.js」的函数「vuexInit」中定义,在Vuex注册部分介绍过,指向Store对象。
- this.\$store.state即调用Store对象的state属性。这时将触发并调用get取值函数,返回this._vm._data.$$state (这时this指向store对象,因为这是在类中的this指针)
- 相当于调用this.\$store._vm._data.\$\$state,其中_vm在resetStoreVM函数中定义,是一个Vue实例。_vm._data.\$\$state,即这个Vue实例中,通过data定义的一个$$state变量。
- 而这个$$state变量指向的是Store对象的state变量(this._modules.root.state,在构造函数中有介绍),为什么要这样放在Vue中,后续会介绍到。
- 而根模块的state变量,在模块安装函数(installModule)中,通过递归定义,将各个层级的state变量,都通过树结构组织起来,树的根,或者说树状态的索引入口就是这个根模块的state变量。
- 也就是this.\$store._vm._data.$$state。也就是this.$store.state。
- this.$store在「mixin.js」的函数「vuexInit」中定义,在Vuex注册部分介绍过,指向Store对象。
- 所以Vuex 使用单一状态树,通过State树结构保存了Vuex中的所有数据
为什么state需要通过Vue的data进行保存?
- 因为我们看到在使用state时,是放在computed中使用, return this.$store.state.count
- 因为要实现数据响应,当state的值发生变化时,需要通知对应的computed函数。
- 这个功能就要借助vue的数据绑定功能,所以要在data中定义。至于底层原理是什么,请关注后续文章,vue源码解读
- 因为我们看到在使用state时,是放在computed中使用, return this.$store.state.count
- Vue子组件的注册,是通过minix混合功能来实现,具体原理在「第一章」中介绍过
- state的定义是通过class的取值函数getter及存值函数setter来完成的。取值设值函数的功能,和Object.defineProperties函数类似,相当于设置拦截器,当获取值或设置值时,调用对应函数。
- 先来看看源码,辅助函数的定义在helpers.js文件中。
normalizeNamespace函数,所有辅助函数公用,用于适配「单纯的map写法」以及「带上命名空间的写法」。
- 「单纯的map写法」是指没有设置命名空间前缀,直接索取需要的变量,如
- 「带上命名空间的写法」即调用 createNamespacedHelpers 返回带上命名空间前缀的辅助函数。即官网介绍的这个
- createNamespacedHelpers的内部,是将复制函数的第一个参数填写为null,第二个为命名空间前缀
- 而我们看回各辅助函数的定义,是通过normalizeNamespace函数生成的,即相当于往normalizeNamespace函数中,第一个参数填写为null,第二个为命名空间前缀
- normalizeNamespace函数,是对命名空间前缀的识别和兼容,bind(null)是为了不改变this的指向,让this仍然指向vue组件,参数二是命名空间前缀。这是辅助函数的第一个参数namespace就有了值。
- 通过bind函数,使得后续传递参数时,后续使用时,从参数的第二个开始填充,此项技术为偏函数
- 通过偏函数,使得在实际使用时,直接传递所需的属性,与「单纯的map写法」统一用法
- 下面介绍的是没有「单纯的map写法」的流程,「带上命名空间的写法」如此类推
- 「单纯的map写法」是指没有设置命名空间前缀,直接索取需要的变量,如
- 回调函数参数介绍,namespace是命名空间前缀,states是在调用mapState时,用户传递的内容,可以一个是包含函数,或者键值对的对象,或者一个单纯的key数组。
- 定义一个对象res
normalizeMap函数,同样所有辅助函数公用,用于兼容数组写法和对象写法。
- 将内容均解析为key,val对象,例如
- normalizeMap([a, b, c]) => [ { key: a, val: a }, { key: b, val: b }, { key: c, val: c } ]
- normalizeMap({a: 1, b: 2, c: 3}) => [ { key: 'a', val: 1 }, { key: 'b', val: 2 }, { key: 'c', val: 3 } ]
- 这也为什么能兼容多种传值方式
对返回的每个key,val对象,调用回调函数。往res对象中,添加名为上面生成的key的函数
找到state和getters,并判断是否设置命名空间,返回不同的值。有命名空间则通过 getModuleByNamespace 函数返回,
- 拿到具体模块内部的context变量中的state和getters(实际上也是this.$store.state中的内容,只是添加了命名空间前缀)
- 兼容函数写法和对象或数组写法,数组或对象,则直接返回值。函数则返回一个绑定了this的函数,并出入state和getters,对应官网,官网的介绍,缺了对参数二getters的介绍,其实如果传入函数时,函数可以接受第二个参数,getter。
- res[key].vuex = true // 函数标识符,开发中没什么用,在调试工具中使用。
- 由于返回的是一个res对象,所以可以通过对象展开运算符,展开每一个对象属性。
- 由于res对象的属性都是一个个函数,所以用在computed中定义计算属性,而不是放在data中定义。
接着介绍的是getter
通过store 的计算属性,例如,this.$store.getters.doneTodosCount
- this.$store.getters,即store对象的getters属性,坑爹的是,getters也不是在构造函数中定义的。getters在resetStoreVM中定义的。
- 通过Object.defineProperty函数,为getters的属性定义拦截器,返回store._vm[key]
- 而store._vm[key],即调用store内部的vue组件的属性,对应的属性,通过了computed 计算属性去定义,计算属性的函数即为getter函数本身
- 所以官网介绍「Vuex 允许我们在 store 中定义“getter”(可以认为是 store 的计算属性)」,其实本身就是计算属性,所以才能将计算结果缓存起来。
- this.$store.getters,即store对象的getters属性,坑爹的是,getters也不是在构造函数中定义的。getters在resetStoreVM中定义的。
通过属性访问,例如,this.$store.getters.doneTodosCount
- 正如刚刚说的,是在computed定义,当然可以通过对象的方式访问。就好比我们在vue中在computed定义属性,在函数中引用。
通过方法访问,例如,this.$store.getters.getTodoById(2)
- 即在函数中,返回函数,没有好讲的。
- 大体方法和mapsState类似,同样是同命名空间进行了一些兼容,再同全局getter容器中返回this.$store.getters。
Mutation
示例,store.commit('increment')运行流程
- 官网中的store,是直接使用Store对象,放在vue组件中,则通过this.$store访问。
- commit的定义在store的构造函数中,commit函数是绑定了this为store的函数,绑定this是为了在辅助函数使用时,this指针不被改变,前介绍过,bind(null)。
调用commit函数时,
- 从_mutations容器中,获取与commit提交的mutation函数同名的数组,即保存同名函数的数组。
- 调用_withCommit,执行commit时,将状态设置为committing。通过committing标示符,使得其他修改state的都是非法操作。
对数组中的每一个函数进行调用,并传入负载参数,对应官网提交载荷(Payload)。
- 为什么在定义mutation中,还有一个state变量呢?
- 这因为在注册Mutation函数函数时(registerMutation函数),已经通过call函数,local.state放入了函数的第一个参数中。
- this._subscribers是在插件中使用的,对每个commit函数的进行监听,订阅 store 的 mutation。handler 会在每个 mutation 完成后调用,该功能常用于插件。
- 这个特性是在commit函数第一句被设置的,通过unifyObjectStyle函数兼容对象写法和负载参数写法。
- unifyObjectStyle函数的原理就是,判断参数是否为对象,是对象则进行解析,并调整参数位置。
- 其实这个跟mutation没什么直接关系,只是说当mutation中使用到state的某属性时,需要提前在state中定义,而不是中往state插入元素,即使插入,也需要通过vue.set插入。
- 因为store内部,也是通过Vue的date来保存state的。既然想要响应式。自然是需要遵循Vue的规则。
- 使用常量替代 Mutation 事件类型,这是代码风格问题,与逻辑无关。
- 因为在store的commit里面,是对mutaion的简单调用,并没有设置回调函数,或者promise resolve。所以必须是同步函数。
- 大体方法和mapsState类似,同样是同命名空间进行了一些兼容,再同全局_mutation容器中返回,没什么好讲的。
action
module
- module部分均在介绍模块的配置,属于配置过程,在Vuex的初始化中生效,没有太多的运行逻辑。
总结
- Vuex本身原理很简单,但是为了模块化,加上了命名空间,添加一堆适配的代码。严重增加了代码复杂度。
工作繁忙,断断续续,历时一个月,终于写完。
- 累
- 希望能大家理解Vuex源码
- 文章繁琐,不用打我
- 文章有一定纰漏,欢迎指正
各位大佬,觉得OK的话,帮忙点个赞呗~
总目录
- 0-全局介绍
- 1-Vuex的安装
2-Store的创建及模块树介绍
- 2.1-installModule模块安装及内容创建
- 2.2-resetStoreVM数据响应式的实现
- 3-Vuex官方文档对照说明
相关推荐
CSCCockroach 2020-09-15
lbPro0 2020-07-05
MrSunOcean 2020-06-21
lylwanan 2020-06-14
Callmesmallpure 2020-05-31
ShaLiWa 2020-05-25
墨龙吟 2020-04-24
MrSunOcean 2020-04-24
H女王范儿 2020-04-22
lbPro0 2020-04-16
ShaLiWa 2020-02-29
ShaLiWa 2020-01-17
MrSunOcean 2020-01-03
lbPro0 2020-01-01
H女王范儿 2019-12-29