最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • vuex源码分析(二)

    正文概述 掘金(cherish553)   2021-01-10   455

    二、模块初始化

    在Store的constructor中,会执行一系列数据的初始化,其中会执行this._modules = new ModuleCollection(options),假设写入的option是这样的结构

    const modulesA = {
      namespaced: true,
      state: {
        conut: 1
      },
      getters: {
        computedConut(state) {
          return state.conut + 1
        }
      },
      mutations: {
        add(state) {
          state.conut + 1
        }
      },
      actions: {
        addCount(context) {
          context.commit('add')
        }
      }
    }
    const store = new Vuex.Store({
      modules: {
        modulesA
      },
      state: {
        conut: 1
      },
      getters: {
        computedConut(state) {
          return state.conut + 1
        }
      },
      mutations: {
        add(state) {
          state.conut + 1
        }
      },
      actions: {
        addCount(context) {
          context.commit('add')
        }
      }
    })
    

    在执行ModuleCollection的constructor的时候,参数rawRootModule就是之前的options,constructor会执行this.register,第一参数传入空数组,第二个参数传入rawRootModule,第三个参数传false。在这里作者给出的注释是// register root module (Vuex.Store options)注册根部module。register函数首先会通过const newModule = new Module(rawModule, runtime),rawModule为rawRootModule,runtime为false。Module在执行constructor的时候会执行this._rawModule = rawModule来保存传入的rawModule, 执行const rawState = rawModule.state来保存rawModule为的state,也就是传入的对象根部的state,接着通过this.state = (typeof rawState === 'function' ? rawState() : rawState) || {}来保存state,也就是说根部的state既可以是对象的形式,也可以是一个函数执行后返回对象的形式。这样register函数的newModule就是一个Module实例。接着register函数会判断path的长度,path在首次执行传入的是一个空数组,也就是会执行this.root = newModule把我们实例化的newModule作为root。接着会判断rawModule.modules在上边的demo中,是有编写modules的,所以会通过Object.keys遍历rawModule.modules,去执行 this.register(path.concat(key), rawChildModule, runtime),path在第一次是一个空数组,但执行到了这一步,会通过concat,把遍历到的modules的key添加到数组中,这样path就成了['modulesA'],然后第二个参数为modulesA的值。这样递归的执行register函数进行注册,和第一次执行register不同的是,会先去执行const parent = this.get(path.slice(0, -1)),也就是执行get方法,由于对path进行了剔除最后一位的操作,所以传入的是一个空数组。get函数会对传入的path做一个reduce,reduce每次执行会把当前的key作为参数传入Module实例的getChild方法,执行并返回结果,Module实例的getChild方法会返回当前this._children[key]。对于本次执行get由于传入的path参数是一个空数组,所以会直接返回this.root,那么parent就是第一次执行registerModule实例。接着本次的register会执行parent.addChild(path[path.length - 1], newModule),第一个参数传入path的最后一位,也就是moduleA字符串,第二个参数传入的是本次register的Module实例,addChild会执行this._children[key] = module这样就把ModuleA创建的Module实例存入了根部Module实例的_children中,那么假如modulesA的modules执行到get方法的时候,就可以拿到modulesA的module实例作为parent,继续执行appendchild操作,这样就递归建立了Module实例之间的父子关系。

    // src/module/module-collection.js
    class ModuleCollection {
      constructor (rawRootModule) {
        // register root module (Vuex.Store options)
        this.register([], rawRootModule, false)
      }
      get (path) {
        return path.reduce((module, key) => {
          return module.getChild(key)
        }, this.root)
      }
      ...
      register (path, rawModule, runtime = true) {
        ...
        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)
          })
        }
      }
      ...
    }
    // src/module/module.js
    export default class Module {
      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) || {}
      }
      ...
      getChild (key) {
        return this._children[key]
      }
      ...
    }
    

    三、模块安装

    执行new ModuleCollection(options)会为store实例的_modules挂载,之后会执行installModule函数,作者给出的注释是// init root module.,// this also recursively registers all sub-modules以及// and collects all module getters inside this._wrappedGetters,也就是说注册根部模块并且递归的安装所有子模块,并且会把所有模块的getter收集到this._wrappedGetters中。

    1、执行makeLocalContext拿到local

    执行installModule的时候第一个参数传入当前的store实例,第二个参数传入根部的state,第三个参数传入空数组,第四个参数传入根部的module实例。installModule函数首先执行const isRoot = !path.length,根据传入的path.length(首次是空数组)判断他是不是root,然后执行 const namespace = store._modules.getNamespace(path)getNamespace函数会根据传入的path数组,获取他们的module,如果namespaced为true,那么会返回key+/,假如传入的path为['modulesA','modulesB'],并且两者namespaced都为true,则会返回modulesA/modulesB/,首次执行传入的path为空数组,所以首次namespace为一个空的字符串。接着判断如果第四个参数modulenamespaced为true,那么会执行store._modulesNamespaceMap[namespace] ,也就是说store._modulesNamespaceMap对象中存储的是namespaced为true的module实例,并且以namespace作为key。接着会执行const local = module.context = makeLocalContext(store, namespace, path)makeLocalContext函数整体看下来定义了一个local对象,其中有dispatchcommit,接通过Object.defineProperties为local定义了访问getters和state的返回结果,最终返回local。dispatch和commit都会判断Namespace,如果不是一个空的字符串,也就是说module的namespaced为false,会直接注册,如果有Namespace那么会执行type = namespace + type,也就是说在vuex中注册的带有namesped的module中的mutationsactions最终访问的方式会是modulesA/modulesB/methods,也就是说makeLocalContext函数为我们重写了commit和dispatch。之后定义的getter的访问方式同样也是通过Namespace来判断,如果有,则会执行makeLocalGettersmakeLocalGetters会判断store._makeLocalGettersCache[namespace],也就是是否有了当前module的getter缓存,如果没有则会遍历getters,首先拿到key最后的getter名称,然后通过 Object.defineProperty把当前的getter和全局的getter进行绑定,这样当访问全局的比如modulesA/modulesB/xxxx就会访问到当前module的getter。state的访问方式和之前的不同,他是通过state.ModulesA.ModulesB.xxx进行访问,对应getNestedState函数,去执行return path.reduce((state, key) => state[key], state),这样当访问子模块的state,会一层层去找,最终返回访问module的state。这样在配置store的时候,我们无需去关注他的嵌套层级。

    // src/store.js
    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
      }
      ...
      const local = module.context = makeLocalContext(store, namespace, path)
      ...
    }
    
    /**
     * 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) => {
          ...
          if (!options || !options.root) {
            type = namespace + type
            ...
          }
          return store.dispatch(type, payload)
        },
        commit: noNamespace ? store.commit : (_type, _payload, _options) => {
          ...
          if (!options || !options.root) {
            type = namespace + type
            ...
          }
          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
    }
    
    function makeLocalGetters (store, namespace) {
      if (!store._makeLocalGettersCache[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
          })
        })
        store._makeLocalGettersCache[namespace] = gettersProxy
      }
    
      return store._makeLocalGettersCache[namespace]
    }
    

    起源地下载网 » vuex源码分析(二)

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元