美文网首页
伪前端带你不完全解析Vuex源码(一)

伪前端带你不完全解析Vuex源码(一)

作者: 万物论 | 来源:发表于2018-12-30 14:19 被阅读9次

    Vuex是什么?
    大家可以先把github上的 vuex项目 clone下来或者直接下载下来用编辑器打开

    git clone https://github.com/vuejs/vuex.git
    
    Vuex项目目录
    项目的目录结构很清晰,重点会解析
    module-collection.js module.js index.js mixin.js store.js这几个js文件。

    当我们要使用vuex管理状态的时候就要规划好vuex的结构目录了,对于大型的vue应用官方也有推荐的项目结构:

    ├── index.html
    ├── main.js
    ├── api
    │   └── ... # 抽取出API请求
    ├── components
    │   ├── App.vue
    │   └── ...
    └── store
        ├── index.js          # 我们组装模块并导出 store 的地方
        ├── actions.js        # 根级别的 action
        ├── mutations.js      # 根级别的 mutation
        └── modules
            ├── cart.js       # 购物车模块
            └── products.js   # 产品模块
    

    结构中store目录下面的index.js文件就是用来生成一个管理状态store对象
    通常我们会在store/index.js文件中敲入这些代码

    import Vue from 'vue'
    import Vuex from 'vuex'
    import state from './state'
    import mucations from './mucations'
    import * as actions from './actions'
    import * as getters from './getters'
    
    const debug = process.env.NODE_ENV !== 'production'
    
    Vue.use(Vuex)
    
    export default new Vuex.Store({
      state,
      mucations,
      actions,
      getters,
      strict: debug
    })
    

    import Vuex from 'vuex' 就是导入Vuex项目中的入口文件index.js中的接口


    到这边就要对Vuex项目中的源码进行不完全解析,让我们回到Vuex项目中

    入口

    // index.js
    import { Store, install } from './store'
    import { mapState, mapMutations, mapGetters, mapActions, createNamespacedHelpers } from './helpers'
    
    export default {
      Store,
      install,
      version: '__VERSION__',
      mapState,
      mapMutations,
      mapGetters,
      mapActions,
      createNamespacedHelpers
    }
    

    这个就是Vuex对外暴露的接口,其中 Store就是Vuex提供的状态管理类,稍后会进行介绍,再来看看install,这个函数会在Vue.use(Vuex)的时候调用。

    查看src/store.js里面的install

    export function install (_Vue) {
      if (Vue && _Vue === Vue) {
        if (process.env.NODE_ENV !== 'production') {
          console.error(
            '[vuex] already installed. Vue.use(Vuex) should be called only once.'
          )
        }
        return
      }
      Vue = _Vue
      applyMixin(Vue)
    }
    

    _Vue就是Vue.use(Vuex)调用install的时候传入的Vue引用,在index.js开头会定义一个局部的Vue引用,用于Vuex中使用

    // ...
    let Vue // bind on install
    // ...
    

    在执行install的时候会判断局部Vue引用是否跟传入的Vue引用是一样的,如果是一样的说明Vuex已经加载了,在非生产环境下会报错,否则让局部Vue也指向传来的Vue,便于Vuex内部使用Vue,函数的最后,又调用了applyMixin(Vue),这个函数是实现通过this.$store.xxx这种方式来访问数据和管理数据,具体怎么实现查看在src/mixin.js代码

    // mixin.js
    export default function (Vue) {
      const version = Number(Vue.version.split('.')[0])
    
      if (version >= 2) {
        Vue.mixin({ beforeCreate: vuexInit })
      } else {
        // override init and inject vuex init procedure
        // for 1.x backwards compatibility.
        const _init = Vue.prototype._init
        Vue.prototype._init = function (options = {}) {
          options.init = options.init
            ? [vuexInit].concat(options.init)
            : vuexInit
          _init.call(this, options)
        }
      }
    

    由于vue1.x和vue2.x的生命周期钩子有不同,所以会进行版本的判断,让vue在初始化(vue2.x是beforeCreate,vue1.x是init)钩子函数数组中mixin一个vuexInit函数,实现Vue组件初始化的时候执行vueInit往vue组件中注入$store这样我们就可以在vue组件中使用 this.$store.xxx访问到store管理对象中的数据和方法,具体怎么在vue组件中注入$store属性,查看vuexInit函数中的代码

      /**
       * Vuex init hook, injected into each instances init hooks list.
       */
    
      function vuexInit () {
        const options = this.$options
        // store injection
        if (options.store) {
          this.$store = typeof options.store === 'function'
            ? options.store()
            : options.store
        } else if (options.parent && options.parent.$store) {
          this.$store = options.parent.$store
        }
      }
    

    上面这段代码会在Vue组件初始化的时候被调用,他会从构造这个vue实例的options参数中判断是否含有store属性,如果有则把这个store注入到vue实例的$store属性中,就比如说我们vue应用的整个项目的入口文件是main.js里面的代码是

    // main.js
    import Vue from 'vue'
    import App from './App.vue'
    import router from './router'
    import store from './store/index'
    
    Vue.config.productionTip = false
    
    new Vue({
      router,
      store,
      render: h => h(App)
    }).$mount('#app')
    

    main.js中创建的vue组件其实是最顶层的vue组件<Root></Root>,这里的if (options.store)就是判断在new Vue({...})传给Vue构造函数的参数也就是options中是否有store这个对象,如果有就注入到这个顶层vue实例的$store的属性中,这时候最顶层的vue组件<Root><App>...</App></Root>Root中就包含了$store,因为每个vue组件被创建之前都会调用beforeCreat/Init生命周期下的所有函数,包括被mixin到里面的vuexInit所以当App组件被创建的时候,他本身的options参数是没有store的,而
    else if (options.parent && options.parent.$store)他会去判断是否含有父组件,这里App的父组件就是Root组件了,而Root有$store属性所以把父组件的这个属性的引用赋值给App这个组件的$store,这样就达到每个vue组件创建的时候都能拿到store,一级一级的传下去。
    看到这里也不是说store只能在最顶层的组件出现,这样是为了在这个项目的所有vue组件都能通过这个状态管理来管理数据,store是可以在任何一个实例化vue组件的options中出现的,只不过这样只能在当前组件和其子孙组件中使用store而已。可以采用Bus等来实现没必要用Vuex。

    到这边就实现了 为什么我们在组件中可以使用this.$store.xxx来访问数据,管理数据来。


    Store类

    接下来针对具体怎么实现状态管理来解析代码
    回到store.js中对Store类进行解析,这个是用来实例化一个store对象的也就是this.$store.xxx中的store

      constructor (options = {}) {
        // Auto install if it is not done yet and `window` has `Vue`.
        // To allow users to avoid auto-installation in some cases,
        // this code should be placed here. See #731
        if (!Vue && typeof window !== 'undefined' && window.Vue) {
          install(window.Vue)
        }
    
        if (process.env.NODE_ENV !== 'production') {
          assert(Vue, `must call Vue.use(Vuex) before creating a store instance.`)
          assert(typeof Promise !== 'undefined', `vuex requires a Promise polyfill in this browser.`)
          assert(this instanceof Store, `store must be called with the new operator.`)
        }
    
        const {
          plugins = [],
          strict = false
        } = options
    
        // store internal state
        this._committing = false
        this._actions = Object.create(null)
        this._actionSubscribers = []
        this._mutations = Object.create(null)
        this._wrappedGetters = Object.create(null)
        this._modules = new ModuleCollection(options)
        this._modulesNamespaceMap = Object.create(null)
        this._subscribers = []
        this._watcherVM = new Vue()
    
        // bind commit and dispatch to self
        const store = this
        const { dispatch, commit } = this
        this.dispatch = function boundDispatch (type, payload) {
          return dispatch.call(store, type, payload)
        }
        this.commit = function boundCommit (type, payload, options) {
          return commit.call(store, type, payload, options)
        }
    
        // strict mode
        this.strict = strict
    
        const state = this._modules.root.state
    
        // init root module.
        // this also recursively registers all sub-modules
        // and collects all module getters inside this._wrappedGetters
        installModule(this, state, [], this._modules.root)
    
        // initialize the store vm, which is responsible for the reactivity
        // (also registers _wrappedGetters as computed properties)
        resetStoreVM(this, state)
    
        // apply plugins
        plugins.forEach(plugin => plugin(this))
    
        const useDevtools = options.devtools !== undefined ? options.devtools : Vue.config.devtools
        if (useDevtools) {
          devtoolPlugin(this)
        }
      }
    

    在构建一个 store实例之前要确保Vuex已经被加载,所以一开始的

      if (!Vue && typeof window !== 'undefined' && window.Vue) {
        install(window.Vue)
      }
    

    这段代码判断没加载就调用install函数加载Vuex原理和上面解析install的时候一样。

    下面的代码是用来判断依赖

    if (process.env.NODE_ENV !== 'production') {
        assert(Vue, `must call Vue.use(Vuex) before creating a store instance.`)
        assert(typeof Promise !== 'undefined', `vuex requires a Promise polyfill in this browser.`)
        assert(this instanceof Store, `store must be called with the new operator.`)
     }
    

    里面的assert是他们自己实现的一个断言函数,代码比较简单,可以查看utils.js里面的函数实现。
    先判断Vue因为里面用到的比如刚才的往组件中注入$store属性或者等下要介绍的数据监听等等功能都依赖Vue。还要断言环境是否支持Promise,在actions中会用到Promise所以Promise也必须被支持,最后一个断言是 当前的store对象,必须是通过Store类创建的。

    const {
       plugins = [],
       strict = false
    } = options
    

    这段代码则是通过ES6的解构赋值,把构造store store = new Vuex.Store({...})时候的参数options,赋值成局部可引用的变量

      // store internal state
        this._committing = false
        this._actions = Object.create(null)
        this._actionSubscribers = []
        this._mutations = Object.create(null)
        this._wrappedGetters = Object.create(null)
        this._modules = new ModuleCollection(options)
        this._modulesNamespaceMap = Object.create(null)
        this._subscribers = []
        this._watcherVM = new Vue()
    

    _committing:用来判断是否是通过commit来实现更改数据因为commit是修改state中数据的唯一合法途径
    _actions:存放把我们store/actions下面编写的所有action函数通过注册后的函数集合,就是注册后的action
    _actionSubscribers:订阅了action变化的订阅者集合
    _mutations:存放把我们store/mutations下面编写的所有mutation函数通过注册后的函数集合,就是注册后的mutation
    _wrappedGetters: 对我们编写的getters函数进行包装后的函数,用于组册getters
    _modules:当我们的项目很庞大的时候,很多数据都要用状态管理,这时候把所有的数据都存在一个store进行管理会显得臃肿而且复杂,变得难以管理,所以Vuex提供了module,也就是每个module下包含各自的state,actions,getters,mutations。他是根据ModuleCollection这个类生产一个module树,接下来会对这个类进行解析
    _modulesNaspaceMap:用于存放命名空间下对应的module
    _subscribers:用来存储所有对 mutation 变化的订阅者
    _watcherVM:一个vue实例,利用vue的watch来监测state数据的变化

    至于Object.create(null)是创建一个空对象但是又有别于var obj = {}中这种通过字面量创建的空对象,因为Object.create(null)创建的空对象的__proto__是指向null的,Object.create创建对象的大概过程就是:

    function (o) {
      var F = function () {}
      F.prototype = o
      var f = new F()
      return f
    }
    
    this._modules = new ModuleCollection(options)
    

    通过把options也就是创建store时候的参数传给ModuleCollection构建module树

    // module-collection.js
      constructor (rawRootModule) {
        // register root module (Vuex.Store options)
        this.register([], rawRootModule, false)
      }
    

    这个是ModuleCollection的构造器,rawRootModule就是我们前面传将来的options,构造器通过调用register()函数来注册module构建module树

    // module-collection.js
    register (path, rawModule, runtime = true) {
        if (process.env.NODE_ENV !== 'production') {
          assertRawModule(path, rawModule)
        }
    
        const newModule = new Module(rawModule, runtime)
        if (path.length === 0) {
          this.root = newModule
        } else {
          const parent = this.get(path.slice(0, -1))
          parent.addChild(path[path.length - 1], newModule)
        }
    
        // register nested modules
        if (rawModule.modules) {
          forEachValue(rawModule.modules, (rawChildModule, key) => {
            this.register(path.concat(key), rawChildModule, runtime)
          })
        }
      }
    

    比如我们一开始传过来的参数是register([], rawRootModule, false)第一个参数是用来表示也可以说是层级关系,rawModule则是用于构造一个module所用的参数,runtime表示mudule是否是运行时。
    我们先分析为根module的情况,也就是rawModule为构建store时候传进来的option,一开始也是先对传进来的rawModule先做断言,看是不是符合构造一个module,这边的代码也是比较好理解的这边就不解析了。
    通过 const newModule = new Module(rawModule, runtime)创建一个module对象
    来看看Module代码

    // module.js
      constructor (rawModule, runtime) {
        this.runtime = runtime
        // Store some children item
        this._children = Object.create(null)
        // Store the origin module object which passed by programmer
        this._rawModule = rawModule
        const rawState = rawModule.state
    
        // Store the origin module's state
        this.state = (typeof rawState === 'function' ? rawState() : rawState) || {}
      }
    

    设置这个module的运行时状态为false,因为一开始这个根module的子module还没注册所以为空,所以先this.children = Object.create(null)赋值个空对象,还有设置这个module的state属性,也就是根module的state。
    然后再回到module-collection.js中,这时候这个 newModule就是根module了,因为一开始path === []所以设置这个store的root为根module。接下来会判断这个rawModule是否配置了子module,如果没有则整个module就一个root module,如果有子module,forEachValue也是他们自己封装的枚举对象的,具体可以查看 utils.js他把rawModule.modules的对象当作构建子module的参数,还有把当前子module的key放到path中递归调用register从而实现module树的构建。假如刚才创建的root还存在子module的话,第二次调用register,假设rawModule.modules存在moa: {...}这样一个module配置的话,那么第二次调用register传进去的参数就是,register(['moa'], moa, runtime)然后再通过 new Module() 创建一个module对象,然而这时候path.length !== 0
    通过const parent = this.get(path.slice(0, -1))来获取moa这个module的父module

    get (path) {
    // module-collection.js
       return path.reduce((module, key) => {
         return module.getChild(key)
       }, this.root)
     }
    

    这段代码就是用来获取当前module的父module,因为要找父module肯定不能包含自己,所以通过path.slice(0, -1)传到get函数中的时候就不包含自己了,由于这时候get中path为空数组所以直接返回this.root就是根module,这时候parent === this.root为true,然后在这个父module中的_childre添加当前的newModule,key就是moa,这时候root就有子module了。递归直到没有其他module配置为止,就完成了module树的构建。

    现在再回到store.js这个文件中,
    this._modules = new ModuleCollection(options)这个_modules就是刚才构建出来的module树了。

    // store.js
    // bind commit and dispatch to self
        const store = this
        const { dispatch, commit } = this
        this.dispatch = function boundDispatch (type, payload) {
          return dispatch.call(store, type, payload)
        }
        this.commit = function boundCommit (type, payload, options) {
          return commit.call(store, type, payload, options)
        }
    

    让store这个局部变量指向store对象,然后对store里面的dispatch和commit绑定当前的store对象。也就是这两个函数里面的this都是指向这个store对象

    // store.js
     commit (_type, _payload, _options) {
       // check object-style commit
       const {
         type,
         payload,
         options
       } = unifyObjectStyle(_type, _payload, _options)
    
       const mutation = { type, payload }
       const entry = this._mutations[type]
       if (!entry) {
         if (process.env.NODE_ENV !== 'production') {
           console.error(`[vuex] unknown mutation type: ${type}`)
         }
         return
       }
       this._withCommit(() => {
         entry.forEach(function commitIterator (handler) {
           handler(payload)
         })
       })
       this._subscribers.forEach(sub => sub(mutation, this.state))
    
       if (
         process.env.NODE_ENV !== 'production' &&
         options && options.silent
       ) {
         console.warn(
           `[vuex] mutation type: ${type}. Silent option has been removed. ` +
           'Use the filter functionality in the vue-devtools'
         )
       }
     }
    

    利用unifyObjectStyle这个函数来统一参数,因为我们commit的时候有两种参数方式具体可以查看Vuex的官网
    通过参数构建一个mutation对象,从组册的mutatons中通过type也就是我们定义mutation时候的函数名字,拿到同名的mutation集合entry,为什么说同名呢,下面在安装module的时候会讲到,会先对这entry进行判断如果没有这个mutation则报错,接下来会调用,_withCommit()这个函数进行state的改变,传进一个遍历执行对于mutation的函数其中payload就是我们定义mutation时候的函数的参数。

      _withCommit (fn) {
        const committing = this._committing
        this._committing = true
        fn()
        this._committing = committing
      }
    

    其实这个函数不复杂,我们在commit的时候去改变_committing的状态,为true则表示正在通过commit方式改变state,用于在watch监听数据改变的时候判断是否是通过commit来改变的。然后执行我们注册的mutation函数。
    回到commit函数this._subscribers.forEach(sub => sub(mutation, this.state))遍历通知订阅了mutation的订阅者,传入最新的state。

      dispatch (_type, _payload) {
        // check object-style dispatch
        const {
          type,
          payload
        } = unifyObjectStyle(_type, _payload)
    
        const action = { type, payload }
        const entry = this._actions[type]
        if (!entry) {
          if (process.env.NODE_ENV !== 'production') {
            console.error(`[vuex] unknown action type: ${type}`)
          }
          return
        }
    
        this._actionSubscribers.forEach(sub => sub(action, this.state))
    
        return entry.length > 1
          ? Promise.all(entry.map(handler => handler(payload)))
          : entry[0](payload)
      }
    

    dispatch一开始跟commit是类似的,不同的是最后return那边,会先判断同名的action有几个,如果是一个就是直接执行entry中的第一个action,如果大于一个,我们都知道我们可以在action中进行异步的操作,所以如果action中有reutrn数据的话,由于异步的关系,你return出来的数据不一定是你想要的结果,所以在组册action的时候他的包装函数会把不是Promise结果处理成Promise,Promise.all(),用来确保全部的注册的action函数执行,也就是状态都是resolve否则dispatch不会返回正确的值。所以你获取到var d = dispatch(xxx,xxx)值后可以通过d.then(res => {})来获取对于同名的action集合return的值就是res是个数组,所以这边就解释了为什么一开始要判断是否支持Promise了,Promise也是属于ES6的内容。

    接着回到Store的构造器中

    // strict mode
        this.strict = strict
    
        const state = this._modules.root.state
    

    如果开启严格模式的话,Vuex就会监听数据的改变是否是通过commit。
    然后把root moudle的状态赋值给一个局部变量

    // init root module.
        // this also recursively registers all sub-modules
        // and collects all module getters inside this._wrappedGetters
        installModule(this, state, [], this._modules.root)
    

    这边开始安装modules,我们一开始只是构建了module树,但是mutations,actions,getters都还没注册,而且如果含有多个module的话,状态state树也还没构建。通过installModule函数来完成这些事情。

    function installModule (store, rootState, path, module, hot) {
      const isRoot = !path.length
      const namespace = store._modules.getNamespace(path)
    
      // register in namespace map
      if (module.namespaced) {
        store._modulesNamespaceMap[namespace] = module
      }
    
      // set state
      if (!isRoot && !hot) {
        const parentState = getNestedState(rootState, path.slice(0, -1))
        const moduleName = path[path.length - 1]
        store._withCommit(() => {
          Vue.set(parentState, moduleName, module.state)
        })
      }
    
      const local = module.context = makeLocalContext(store, namespace, path)
    
      module.forEachMutation((mutation, key) => {
        const namespacedType = namespace + key
        registerMutation(store, namespacedType, mutation, local)
      })
    
      module.forEachAction((action, key) => {
        const type = action.root ? key : namespace + key
        const handler = action.handler || action
        registerAction(store, type, handler, local)
      })
    
      module.forEachGetter((getter, key) => {
        const namespacedType = namespace + key
        registerGetter(store, namespacedType, getter, local)
      })
    
      module.forEachChild((child, key) => {
        installModule(store, rootState, path.concat(key), child, hot)
      })
    }
    

    安装module也是从根module开始 也就是从root开始,参数中,store为当前store对象,rootState为root的state,path就是用来记录module层级关系,module当前要安装的module,hot为热更新。
    当安装root module的时候,通过path判断是否是根module,获得当前module的命名空间,比如我之前root下面还有一个moa的module,如果这个module的namespaced为true的时候得到的命名空间就是moa/。如果moa下面还有一个moa1的module,他的namespaced也为true那么这个moa1的命名空间就是moa/moa1/,所以要把当前module的名称加到命名空间中必须当前module的namespaced = true才可以,比如说moa他的namespaced 为 false 那么moa的命名空间为空,而且moa1的namespaced为true,那么moa1的命名空间为moa1/这样moa就没有出现在命名空间中。
    因为我们当前是根module所以 namespace 为空,他设置namespaced也没有意义

      // register in namespace map
      if (module.namespaced) {
        store._modulesNamespaceMap[namespace] = module
      }
    

    这三行代码是把如果定义了命名空间,就把当前的module放入这个_modulesNamespaceMap集合中,key为命名空间,value为当前module
    接下来因为不是根module所以这个代码等到安装子module的时候解析
    const local = module.context = makeLocalContext(store, namespace, path)这行代码的作用就是用来生成,当前module的上下文对象,生成当前module下的dispatch,commit,getters,state

    /**
     * make localized dispatch, commit, getters and state
     * if there is no namespace, just use root ones
     */
    function makeLocalContext (store, namespace, path) {
      const noNamespace = namespace === ''
    
      const local = {
        dispatch: noNamespace ? store.dispatch : (_type, _payload, _options) => {
          const args = unifyObjectStyle(_type, _payload, _options)
          const { payload, options } = args
          let { type } = args
    
          if (!options || !options.root) {
            type = namespace + type
            if (process.env.NODE_ENV !== 'production' && !store._actions[type]) {
              console.error(`[vuex] unknown local action type: ${args.type}, global type: ${type}`)
              return
            }
          }
    
          return store.dispatch(type, payload)
        },
    
        commit: noNamespace ? store.commit : (_type, _payload, _options) => {
          const args = unifyObjectStyle(_type, _payload, _options)
          const { payload, options } = args
          let { type } = args
    
          if (!options || !options.root) {
            type = namespace + type
            if (process.env.NODE_ENV !== 'production' && !store._mutations[type]) {
              console.error(`[vuex] unknown local mutation type: ${args.type}, global type: ${type}`)
              return
            }
          }
    
          store.commit(type, payload, options)
        }
      }
    
      // getters and state object must be gotten lazily
      // because they will be changed by vm update
      Object.defineProperties(local, {
        getters: {
          get: noNamespace
            ? () => store.getters
            : () => makeLocalGetters(store, namespace)
        },
        state: {
          get: () => getNestedState(store.state, path)
        }
      })
    
      return local
    }
    

    参数store为当前的store对象, namespace为当前module的命名空间,path则是用来在state中找到当前module的state。因为在Vuex中没有设置命名空间的话,actions,mutations,getters默认都是组册在全局里面的。具体会在讲解注册他们的时候分析。
    local就是为对应module的actions,mucations,getters,state提供的上下文对象。
    不要忘了我们一开始是安装root module所以是没有命名空间的,所以当前module就是store一开始提供的dispatch,而这个dispatch函数默认执行的是全局的action,如果是有命名空间的module下的dispatch,他对store一开始的dispatch又包装了一层,作用就是自动加上命名空间,这样我们在module里面dispatch的时候只要写action的type就可以对应到这个module下的action而不是全局的,再来看看这个有命名空间下的dispatch多了一个_options参数,这个作用就是实现dispatch('someOtherAction', null, { root: true })这种方式来分发全局里面的action。判断完后再调用store一开始提供的dispatch来分发actionreturn store.dispatch(type, payload)因为这时候的type已经结果判断是否有加上命名空间了。commit的原理也是类似的。然后再用Object.defineProperties(....)在local添加经过命名空间判断后的gettersstate,对于getters的话没有命名空间的话就返回store.getters有命名空间的话通过命名空间获取当前module下的getters,来看看makeLocalGetters这个函数

    function makeLocalGetters (store, namespace) {
      const gettersProxy = {}
    
      const splitPos = namespace.length
      Object.keys(store.getters).forEach(type => {
        // skip if the target getter is not match this namespace
        if (type.slice(0, splitPos) !== namespace) return
    
        // extract local getter type
        const localType = type.slice(splitPos)
    
        // Add a port to the getters proxy.
        // Define as getter property because
        // we do not want to evaluate the getters in this time.
        Object.defineProperty(gettersProxy, localType, {
          get: () => store.getters[type],
          enumerable: true
        })
      })
    
      return gettersProxy
    }
    

    这个函数就是,从store中注册的getters中,因为getters是一个对象,key是命名空间加我们定义的getters的名字,比如moa/moa1/getusername其中moa/moa1是命名空间,getusername是我们定义的getters的名字。所以我们在一个module中调用getters的时候,也不用带上命名空间。
    对于state的话调用了getNestedState来获取当前module的state

    function getNestedState (state, path) {
      return path.length
        ? path.reduce((state, key) => state[key], state)
        : state
    }
    

    这个函数就是用来从state树中来找到对于module的state。
    回到makeLocalContext函数中,他最终返回这个module的上下文对象。用于在注册actions,mutations,getters中用到。
    回到installModule函数中,接下来的三个函数就是用于注册

      module.forEachMutation((mutation, key) => {
        const namespacedType = namespace + key
        registerMutation(store, namespacedType, mutation, local)
      })
    
      module.forEachAction((action, key) => {
        const type = action.root ? key : namespace + key
        const handler = action.handler || action
        registerAction(store, type, handler, local)
      })
    
      module.forEachGetter((getter, key) => {
        const namespacedType = namespace + key
        registerGetter(store, namespacedType, getter, local)
      })
    

    先来说说注册mutation他在store的_mutations中注册的名字就是命名空间+我们定义的mutation名字,看下registerMutation具体怎么注册

    function registerMutation (store, type, handler, local) {
      const entry = store._mutations[type] || (store._mutations[type] = [])
      entry.push(function wrappedMutationHandler (payload) {
        handler.call(store, local.state, payload)
      })
    }
    

    功能很简单,这边我们会知道,原来store中注册的_mutations的值是以数组存放的,这也是为什么如果你没有定义命名空间的module下面commit_mutations中注册的同名mutation的时候,其他的在同个命名空间下的mutation一起执行的原因了。回到代码中,我们编写的,mutation会经过一个包装函数包装,其中handler,就是我们编写的mutation,这里也会知道为什么我们编写mutation的时候,参数中的state就是当前module下定义的state,而payload就是我们commit的时候带的参数。

    function registerAction (store, type, handler, local) {
      const entry = store._actions[type] || (store._actions[type] = [])
      entry.push(function wrappedActionHandler (payload, cb) {
        let res = handler.call(store, {
          dispatch: local.dispatch,
          commit: local.commit,
          getters: local.getters,
          state: local.state,
          rootGetters: store.getters,
          rootState: store.state
        }, payload, cb)
        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
        }
      })
    }
    

    registerAction中其实也是跟registerCommit差不多的实现。这里面我们也会看到,为什么我们在编写action的时候,参数中{dispatch,commit,getters,state,rootGetters,rootState}是怎么来的了,其实cb很少用到,这里的包装函数也会把你返回的结果包装成Promise,在解析dispatch的时候也有提到。

    function registerGetter (store, type, rawGetter, local) {
      if (store._wrappedGetters[type]) {
        if (process.env.NODE_ENV !== 'production') {
          console.error(`[vuex] duplicate getter key: ${type}`)
        }
        return
      }
      store._wrappedGetters[type] = function wrappedGetter (store) {
        return rawGetter(
          local.state, // local state
          local.getters, // local getters
          store.state, // root state
          store.getters // root getters
        )
      }
    }
    

    这里跟注册mutations和actions不同的地方就是他不能重复。其他地方差不多。但是其实这里的getters还没有真正的注册完成,这时候我们this.$store.getters是没有东西的。这个会在 《不完全解析Vuex源码(二)》中会提到。
    再次回到installModule函数中,最后回去遍历module树中的子module,如果有就继续安装

      module.forEachChild((child, key) => {
        installModule(store, rootState, path.concat(key), child, hot)
      })
    

    跟安装root module不同的是installModule中的这几行代码

    // set state
      if (!isRoot && !hot) {
        const parentState = getNestedState(rootState, path.slice(0, -1))
        const moduleName = path[path.length - 1]
        store._withCommit(() => {
          Vue.set(parentState, moduleName, module.state)
        })
      }
    

    作用就是构建store的state树,先获取到当前module的父state,然后在父的state下面加入当前module的state,key就module名称,value就是当前module的state。这里我们也会看到commit是改变state的唯一合法途径了。

    到这边完成了actions,mutations,getters的注册(getters其实不能说是真正的注册)module树的安装。还有很重要的一部分就是利用Vue的功能实现数据的响应,和监测将在(二)中解析。

    答应我一定要在会用Vuex的前提下再去看源码。

    相关文章

      网友评论

          本文标题:伪前端带你不完全解析Vuex源码(一)

          本文链接:https://www.haomeiwen.com/subject/xwqelqtx.html