通俗易懂Vuex源码导读3-Vuex官方文档对照说明

Vuex 的使用解析

回顾前面几章咱们介绍的内容

  • 第零章对Vuex的总体运行思路,重点变量进行了介绍。
  • 第一章介绍了Vuex的安装过程。
  • 上一章介绍了Vuex的初始化过程,在正式使用Vuex前作了哪些准备工做。
  • 这一章,将对照Vuex的官方说明文档,逐一介绍示例代码背后的运行逻辑。

首先介绍的是state

  • 惟一状态树html

    • 惟一:是指整个Vuex数据存储是由Store这一个对象实例完成的。
    • 状态:是指各个数据。
    • 树:指 module 和 state 的树结构。
    • 经过 Store 变量能够查看当前项目的数据存储状况(做者的用意,多是将Vuex做为Vue项目的整个数据存储库,将全部数据内容都交由Vuex进行管理),能够经过Store观察整个项目的数据状况。
    • 经过热重载功能,进行数据的回退,实现时间穿梭的调整功能(本系列文章没有介绍到,有兴趣的同窗能够看看源码。hot关键字相关内容)。
  • 在计算属性中使用state
    不知你是否会疑惑,在Store的构造函数中并无定义类的state属性,为何能够经过store.state获取到state数据呢?vue

    • state的定义是经过class的取值函数getter及存值函数setter来完成的。取值设值函数的功能,和Object.defineProperties函数相似,至关于设置拦截器,当获取值或设置值时,调用对应函数。

      图

    • 在vue组件中,调用this.$store.state.count的运行逻辑是:es6

      • 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。
    • 因此Vuex 使用单一状态树,经过State树结构保存了Vuex中的全部数据
    • 为何state须要经过Vue的data进行保存?vuex

      • 由于咱们看到在使用state时,是放在computed中使用, return this.$store.state.count

        图

      • 由于要实现数据响应,当state的值发生变化时,须要通知对应的computed函数。
      • 这个功能就要借助vue的数据绑定功能,因此要在data中定义。至于底层原理是什么,请关注后续文章,vue源码解读
    • Vue子组件的注册,是经过minix混合功能来实现,具体原理在「第一章」中介绍过
  • mapState辅助函数segmentfault

    • 先来看看源码,辅助函数的定义在helpers.js文件中。
    • normalizeNamespace函数,全部辅助函数公用,用于适配「单纯的map写法」以及「带上命名空间的写法」。api

      • 「单纯的map写法」是指没有设置命名空间前缀,直接索取须要的变量,如

        图

      • 「带上命名空间的写法」即调用 createNamespacedHelpers 返回带上命名空间前缀的辅助函数。即官网介绍的这个

        图

      • createNamespacedHelpers的内部,是将复制函数的第一个参数填写为null,第二个为命名空间前缀

        图

      • 而咱们看回各辅助函数的定义,是经过normalizeNamespace函数生成的,即至关于往normalizeNamespace函数中,第一个参数填写为null,第二个为命名空间前缀
      • normalizeNamespace函数,是对命名空间前缀的识别和兼容,bind(null)是为了避免改变this的指向,让this仍然指向vue组件,参数二是命名空间前缀。这是辅助函数的第一个参数namespace就有了值。
      • 经过bind函数,使得后续传递参数时,后续使用时,从参数的第二个开始填充,此项技术为偏函数
        图
      • 经过偏函数,使得在实际使用时,直接传递所需的属性,与「单纯的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的函数promise

      • 找到state和getters,并判断是否设置命名空间,返回不一样的值。有命名空间则经过 getModuleByNamespace 函数返回,缓存

        • 拿到具体模块内部的context变量中的state和getters(实际上也是this.$store.state中的内容,只是添加了命名空间前缀)
      • 兼容函数写法和对象或数组写法,数组或对象,则直接返回值。函数则返回一个绑定了this的函数,并出入state和getters,对应官网,官网的介绍,缺了对参数二getters的介绍,其实若是传入函数时,函数能够接受第二个参数,getter。
    • res[key].vuex = true // 函数标识符,开发中没什么用,在调试工具中使用。
  • 对象展开符ide

    • 因为返回的是一个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.doneTodosCount

    • 正如刚刚说的,是在computed定义,固然能够经过对象的方式访问。就比如咱们在vue中在computed定义属性,在函数中引用。
  • 经过方法访问,例如,this.$store.getters.getTodoById(2)

    • 即在函数中,返回函数,没有好讲的。
  • mapGetters 辅助函数

    • 大致方法和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 需遵照 Vue 的响应规则

    • 其实这个跟mutation没什么直接关系,只是说当mutation中使用到state的某属性时,须要提早在state中定义,而不是中往state插入元素,即便插入,也须要经过vue.set插入。
    • 由于store内部,也是经过Vue的date来保存state的。既然想要响应式。天然是须要遵循Vue的规则。
  • 使用常量替代 Mutation 事件类型,这是代码风格问题,与逻辑无关。
  • Mutation 必须是同步函数

    • 由于在store的commit里面,是对mutaion的简单调用,并无设置回调函数,或者promise resolve。因此必须是同步函数。
  • 在组件中提交 Mutation

    • 大致方法和mapsState相似,一样是同命名空间进行了一些兼容,再同全局_mutation容器中返回,没什么好讲的。

action

  • 函数参数

    • 定义action时,能够传入不少参数。这些参数是从哪里来的呢?
    • 找到registerAction函数,能够看到是在注册Action的时候传递进入的
      图

module

  • module部分均在介绍模块的配置,属于配置过程,在Vuex的初始化中生效,没有太多的运行逻辑。

总结

  • Vuex自己原理很简单,可是为了模块化,加上了命名空间,添加一堆适配的代码。严重增长了代码复杂度。

    图

工做繁忙,断断续续,历时一个月,终于写完。

  • 但愿能你们理解Vuex源码
  • 文章繁琐,不用打我
  • 文章有必定纰漏,欢迎指正

图

各位大佬,以为OK的话,帮忙点个赞呗~

总目录

相关文章
相关标签/搜索