资讯专栏INFORMATION COLUMN

vuex源码分析(二)

fizz / 1400人阅读

摘要:那后面的部分就容易理解了,使用方法提交。返回的是删除的。然后为定义属性设置为表示可枚举。前存储先前的设置为是用于取消所有的警告和提醒。这样工程量实在是太多了,下次的源码分析将会以我总结的方式来学习源码

继上面讲完contructor函数,我们继续来讲后面的内容

</>复制代码

  1. get state () {
  2. return this._vm._data.$$state
  3. }
  4. set state (v) {
  5. if (process.env.NODE_ENV !== "production") {
  6. assert(false, `Use store.replaceState() to explicit replace store state.`)
  7. }
  8. }

get和set方法都有es6里边的语法,get语法是将对象属性绑定到查询该属性时将被调用的函数,而set语法是查询对象属性将被调用的函数。当state属性发生变化时,就会返回state的值,set用于检测在我们设置state的时候是否是生产环境,如果不是生产环境,就进行断点。

</>复制代码

  1. commit (_type, _payload, _options) {
  2. // check object-style commit
  3. const {
  4. type,
  5. payload,
  6. options
  7. } = unifyObjectStyle(_type, _payload, _options)
  8. const mutation = { type, payload }
  9. const entry = this._mutations[type]
  10. if (!entry) {
  11. if (process.env.NODE_ENV !== "production") {
  12. console.error(`[vuex] unknown mutation type: ${type}`)
  13. }
  14. return
  15. }
  16. this._withCommit(() => {
  17. entry.forEach(function commitIterator (handler) {
  18. handler(payload)
  19. })
  20. })
  21. this._subscribers.forEach(sub => sub(mutation, this.state))
  22. if (
  23. process.env.NODE_ENV !== "production" &&
  24. options && options.silent
  25. ) {
  26. console.warn(
  27. `[vuex] mutation type: ${type}. Silent option has been removed. ` +
  28. "Use the filter functionality in the vue-devtools"
  29. )
  30. }
  31. }

commit函数接受三个参数,_type表示mutaion的类型,_payload表示额外的参数,options表示一些配置。在函数中有函数叫unfyObjectStyle(),来看看他的代码

</>复制代码

  1. function unifyObjectStyle (type, payload, options) {
  2. if (isObject(type) && type.type) {
  3. options = payload
  4. payload = type
  5. type = type.type
  6. }
  7. if (process.env.NODE_ENV !== "production") {
  8. assert(typeof type === "string", `Expects string as the type, but found ${typeof type}.`)
  9. }
  10. return { type, payload, options }
  11. }

这个函数很简单,就是在type为对象的情况下,对传进来的参数进行各自获取。继续看commit函数,根据unifyObjectStyle()处理后的type去找到mutations,withCommient的代码如下

</>复制代码

  1. _withCommit (fn) {
  2. const committing = this._committing
  3. this._committing = true
  4. fn()
  5. this._committing = committing
  6. }

这个函数最重要的部分在于设置this._committing = true为什么这么做呢。是因为我们在观测state的状态的时候,我们可以通过看this._commiting的值来确定state修改是否错误,如果为false那么是不是状态修改就出现了问题呢。
那commit后面的部分就容易理解了,使用this._withCommit方法提交mutation。
this._subscribers是什么内容呢,这在我们上一篇提过是用来存储所有对 mutation 变化的订阅者。遍历this._subscribers进行回调。

</>复制代码

  1. options && options.silent

这儿我也不太懂黄轶老师说的是静默模式:静默模式就是用户不干扰的情况下,计算机自我处理的情况。
下面是是dispatch的代码

</>复制代码

  1. dispatch (_type, _payload) {
  2. // check object-style dispatch
  3. const {
  4. type,
  5. payload
  6. } = unifyObjectStyle(_type, _payload)
  7. const action = { type, payload }
  8. const entry = this._actions[type]
  9. if (!entry) {
  10. if (process.env.NODE_ENV !== "production") {
  11. console.error(`[vuex] unknown action type: ${type}`)
  12. }
  13. return
  14. }
  15. this._actionSubscribers.forEach(sub => sub(action, this.state))
  16. return entry.length > 1
  17. ? Promise.all(entry.map(handler => handler(payload)))
  18. : entry[0](payload)
  19. }

前面几行代码跟commit函数非常相似,取值,然后找到对应action。

this._actionSubscribers
跟前面commit方法不同的是最后用的是这段代码:

</>复制代码

  1. return entry.length > 1
  2. ? Promise.all(entry.map(handler => handler(payload)))
  3. : entry[0](payload)

如果获取到的action的数量不止一个那么就进行Promise.all()遍历每个action进行回调函数回调,前几天看到一道面试题如果一个进程需要消耗8s,
另外一个进程需要消耗3秒,如何才能让两个进行的时间最短,解决方法就是Promise.all([a,b])注意里边的必须是一个数组,这样才能让两个进程同时进行。如果长度为1,则调用entry0

</>复制代码

  1. subscribe (fn) {
  2. return genericSubscribe(fn, this._subscribers)
  3. }
  4. subscribeAction (fn) {
  5. return genericSubscribe(fn, this._actionSubscribers)
  6. }
  7. function genericSubscribe (fn, subs) {
  8. if (subs.indexOf(fn) < 0) {
  9. subs.push(fn)
  10. }
  11. return () => {
  12. const i = subs.indexOf(fn)
  13. if (i > -1) {
  14. subs.splice(i, 1)
  15. }
  16. }
  17. }

来看看genericSubscribe都做了什么,接受两个参数,就是对subs进行调整,如果subs里边没有fn,那么向subs.push(fn)。返回的是删除fn的subs。那么上面subscribe和subscribeAction就是对传入的fn然后经过
genericSubscribe()函数进行处理
watch (getter, cb, options) {

</>复制代码

  1. if (process.env.NODE_ENV !== "production") {
  2. assert(typeof getter === "function", `store.watch only accepts a function.`)
  3. }
  4. return this._watcherVM.$watch(() => getter(this.state, this.getters), cb, options)

}
watch作用是响应式的检测一个getter方法的返回值,当值反生变化的时候,发生回调。
函数首先首先断言watch的getter必须是一个方法,接着利用了vue的实例对象的this._watcherVM的watch方法

</>复制代码

  1. replaceState (state) {
  2. this._withCommit(() => {
  3. this._vm._data.$$state = state
  4. })
  5. }

用于修改state,调用 this._withCommit 方法修改 Store 的 rootState

registerModule

</>复制代码

  1. registerModule (path, rawModule, options = {}) {
  2. if (typeof path === "string") path = [path]
  3. if (process.env.NODE_ENV !== "production") {
  4. assert(Array.isArray(path), `module path must be a string or an Array.`)
  5. assert(path.length > 0, "cannot register the root module by using registerModule.")
  6. }
  7. this._modules.register(path, rawModule)
  8. installModule(this, this.state, path, this._modules.get(path), options.preserveState)
  9. // reset store to update getters...
  10. resetStoreVM(this, this.state)
  11. }

字面意思能看出动态注册Module的意思,动态的意思是什么呢。就是有些异步操作,我们需要使用这个函数就能够动态注册module了。
这儿有一个函数resetStoreVM,我们来看看他的实现方式

</>复制代码

  1. function resetStoreVM (store, state, hot) {
  2. const oldVm = store._vm
  3. // bind store public getters
  4. store.getters = {}
  5. const wrappedGetters = store._wrappedGetters
  6. const computed = {}
  7. forEachValue(wrappedGetters, (fn, key) => {
  8. // use computed to leverage its lazy-caching mechanism
  9. computed[key] = () => fn(store)
  10. Object.defineProperty(store.getters, key, {
  11. get: () => store._vm[key],
  12. enumerable: true // for local getters
  13. })
  14. })
  15. // use a Vue instance to store the state tree
  16. // suppress warnings just in case the user has added
  17. // some funky global mixins
  18. const silent = Vue.config.silent
  19. Vue.config.silent = true
  20. store._vm = new Vue({
  21. data: {
  22. $$state: state
  23. },
  24. computed
  25. })
  26. Vue.config.silent = silent
  27. // enable strict mode for new vm
  28. if (store.strict) {
  29. enableStrictMode(store)
  30. }
  31. if (oldVm) {
  32. if (hot) {
  33. // dispatch changes in all subscribed watchers
  34. // to force getter re-evaluation for hot reloading.
  35. store._withCommit(() => {
  36. oldVm._data.$$state = null
  37. })
  38. }
  39. Vue.nextTick(() => oldVm.$destroy())
  40. }
  41. }

这个函数的意思在于监听数据的变化,首先进行store.getters的初始化。forEachValue循环所有处理过的getters,并且新建computed对象进行存储,通过Object.defineProperty。然后为store.getter定义属性key,enumerable设置为true,表示可枚举。
前存储先前的Vue.config.slient,Vue.config.slient 设置为true是用于取消所有的警告和提醒。

</>复制代码

  1. store._vm = new Vue({
  2. data: {
  3. $$state: state
  4. },
  5. computed
  6. })

这个就很简单了,创建一个Vue的实例,然后把state放到data里边去,computed里边的值用getters填充。

</>复制代码

  1. if (store.strict) {
  2. enableStrictMode(store)
  3. }

如果是严格模式的话,则调用enableStrictMode()
下面是enableStrictMode的代码

</>复制代码

  1. function enableStrictMode (store) {
  2. store._vm.$watch(function () { return this._data.$$state }, () => {
  3. if (process.env.NODE_ENV !== "production") {
  4. assert(store._committing, `Do not mutate vuex store state outside mutation handlers.`)
  5. }
  6. }, { deep: true, sync: true })
  7. }

这个函数做了什么了,看assert写的意识就是,如果设置了严格模式,就不允许用户在mutaion以外的方式进行修改state

</>复制代码

  1. if (oldVm) {
  2. if (hot) {
  3. // dispatch changes in all subscribed watchers
  4. // to force getter re-evaluation for hot reloading.
  5. store._withCommit(() => {
  6. oldVm._data.$$state = null
  7. })
  8. }
  9. Vue.nextTick(() => oldVm.$destroy())
  10. }

如果oldVm存在,并且hot为True的时候,将原有的vm中的state设置为空,所有原来的getters都会重新计算一遍,为什么会这样呢,这在前面我们创建一个vue的实例的时候,将getters作为了computed,我们知道在Vue中,值一旦发生变化,那么computed就会重新变化。

unregisterModule

</>复制代码

  1. unregisterModule (path) {
  2. if (typeof path === "string") path = [path]
  3. if (process.env.NODE_ENV !== "production") {
  4. assert(Array.isArray(path), `module path must be a string or an Array.`)
  5. }
  6. this._modules.unregister(path)
  7. this._withCommit(() => {
  8. const parentState = getNestedState(this.state, path.slice(0, -1))
  9. Vue.delete(parentState, path[path.length - 1])
  10. })
  11. resetStore(this)
  12. }

跟函数registerModule相似,用于动态卸载module。讲讲这里的this._withCommit的作用,
把当前的模块的state对象从父state上删除,最后调用resetStore函数,下面是resetStore函数

</>复制代码

  1. function resetStore (store, hot) {
  2. store._actions = Object.create(null)
  3. store._mutations = Object.create(null)
  4. store._wrappedGetters = Object.create(null)
  5. store._modulesNamespaceMap = Object.create(null)
  6. const state = store.state
  7. // init all modules
  8. installModule(store, state, [], store._modules.root, true)
  9. // reset vm
  10. resetStoreVM(store, state, hot)
  11. }

这个方法的作用就是重置store, installModule就是重新安装_aciton,_mutations,_wrappedGetters这些属性,resetStoreVM然后重置store的_vm对象。

</>复制代码

  1. hotUpdate (newOptions) {
  2. this._modules.update(newOptions)
  3. resetStore(this, true)
  4. }

函数的作用就是热加载新的action和mutation。先更新modules,然后更新Store。
vuex里边最重要的class Store就讲完了。我们在讲讲其他重要的函数。

</>复制代码

  1. export function deepCopy (obj, cache = []) {
  2. // just return if obj is immutable value
  3. if (obj === null || typeof obj !== "object") {
  4. return obj
  5. }
  6. // if obj is hit, it is in circular structure
  7. const hit = find(cache, c => c.original === obj)
  8. if (hit) {
  9. return hit.copy
  10. }
  11. const copy = Array.isArray(obj) ? [] : {}
  12. // put the copy into cache at first
  13. // because we want to refer it in recursive deepCopy
  14. cache.push({
  15. original: obj,
  16. copy
  17. })
  18. Object.keys(obj).forEach(key => {
  19. copy[key] = deepCopy(obj[key], cache)
  20. })
  21. return copy
  22. }

这个函数的原理就是构造一个新的对象,遍历原对象或者数组,递归调用deepCopy。这儿加了一个cache,为什么呢。这是为了提高性能,比如说在Facinaciate中,我们就可以使用这种方法来提高性能,不用每计算一次,又重新计算已经计算过的东西
这次就讲到这里,下次再讲其他重要的函数

下次我决定不采用这种什么函数都讲清楚的方式了。这样工程量实在是太多了,下次的源码分析将会以我总结的方式来学习源码

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/51439.html

相关文章

  • vuex源码分析

    摘要:那后面的部分就容易理解了,使用方法提交。返回的是删除的。然后为定义属性设置为表示可枚举。前存储先前的设置为是用于取消所有的警告和提醒。这样工程量实在是太多了,下次的源码分析将会以我总结的方式来学习源码 继上面讲完contructor函数,我们继续来讲后面的内容 get state () { return this._vm._data.$$state } set state (...

    jeffrey_up 评论0 收藏0
  • Vue源码解析(五)-vuex

    摘要:提供了函数,它把直接映射到我们的组件中,先给出的使用值为值为让我们看看的源码实现规范当前的命名空间。在中,都是同步事务。同步的意义在于这样每一个执行完成后都可以对应到一个新的状态和一样,这样就可以打个存下来,然后就可以随便了。 Vue 组件中获得 Vuex 状态 按官网说法:由于 Vuex 的状态存储是响应式的,从 store 实例中读取状态最简单的方法就是在计算属性中返回某个状态,本...

    calx 评论0 收藏0
  • 关于Vue2一些值得推荐的文章 -- 五、六月份

    摘要:五六月份推荐集合查看最新的请点击集前端最近很火的框架资源定时更新,欢迎一下。苏幕遮燎沈香宋周邦彦燎沈香,消溽暑。鸟雀呼晴,侵晓窥檐语。叶上初阳乾宿雨,水面清圆,一一风荷举。家住吴门,久作长安旅。五月渔郎相忆否。小楫轻舟,梦入芙蓉浦。 五、六月份推荐集合 查看github最新的Vue weekly;请::点击::集web前端最近很火的vue2框架资源;定时更新,欢迎 Star 一下。 苏...

    sutaking 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<