博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
vuex源码分析
阅读量:6428 次
发布时间:2019-06-23

本文共 26080 字,大约阅读时间需要 86 分钟。

大家好,今天给大家带来的是vuex(2.3.1)源码分析,希望能够能跟大家进行交流,欢迎提意见,写的不好的地方欢迎拍砖[github源码地址][1]首先我们先来看看vuex是如何跟vue项目一起结合使用的,以下是官方demo中的一个简单例子复制代码

(1)我们必须先创建一个store

import Vue from 'vue'import Vuex from 'vuex'import { state, mutations } from './mutations'import plugins from './plugins'Vue.use(Vuex)export default new Vuex.Store({  state,  mutations,  plugins})复制代码

(2)将这个store传给vue的实例,这样我们就能够在vue中获取到这个store并且使用它的功能了

import 'babel-polyfill'import Vue from 'vue'import store from './store'import App from './components/App.vue'new Vue({  store, // inject store to all children  el: '#app',  render: h => h(App)})复制代码

以上就是vuex的简单使用方法,然后接下来我们就开始来分析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的功能export出去,相当于定义vuex对外使用的API

  • store.js——vuex的仓库,也是vuex中比较重要的一环

    这个文件比较长,我们可以一点一点来分析:
    总体来说,这个文件做了几件事,定义并导出了Store这个类和install方法,并执行了install这个方法,我们都知道,vue的所有插件都是通过install这个方法来安装的

import applyMixin from './mixin'import devtoolPlugin from './plugins/devtool'import ModuleCollection from './module/module-collection'import { forEachValue, isObject, isPromise, assert } from './util'复制代码

一开始导入相关的方法,后面会解释这些方法的用处

let Vue // 定义了变量Vue,为的是引用外部的vue构造函数,这样vuex框架就可以不用导入vue这个库了复制代码

----------------------------------------------------------这是分割线----------------------------------------------------------------------------------------

接下来是定义Store这个类,从图中可以看出这个vuex中的外store对外提供的能力,包括常用的commit,dispatch,watch等

准备上传

先看看构造函数吧:

constructor (options = {}) {    // 这个是在开发过程中的一些环节判断,vuex要求在创建vuex store实例之前必须先使用这个方法Vue.use(Vuex)来安装vuex,项目必须也得支持promise,store也必须通过new来创建实例        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.`)    }        // 从参数options中结构出相关变量    const {      plugins = [],      strict = false    } = options    let {      state = {}    } = options        // 这个简单的,不解释    if (typeof state === 'function') {      state = state()    }    // store internal state    // 初始化store内部状态,Object.create(null)可以创建一个干净的空对象    this._committing = false    this._actions = Object.create(null)    this._mutations = Object.create(null)    this._wrappedGetters = Object.create(null)    // vuex支持模块,即将state通过key-value的形式拆分为多个模块    // 模块的具体内容可以查看这里 :https://vuex.vuejs.org/en/mutations.html    this._modules = new ModuleCollection(options)    this._modulesNamespaceMap = Object.create(null)    // 监听队列,当执行commit时会执行队列中的函数    this._subscribers = []    // 创建一个vue实例,利用vue的watch的能力,可以监控state的改变,具体后续watch方法会介绍    this._watcherVM = new Vue()    // bind commit and dispatch to self        const store = this    // 缓存dispatch和commit方法    const { dispatch, commit } = this    // 定义dispatch方法    this.dispatch = function boundDispatch (type, payload) {      return dispatch.call(store, type, payload)    }    // 定义commit方法    this.commit = function boundCommit (type, payload, options) {      return commit.call(store, type, payload, options)    }    // strict mode    // 定义严格模式,不要在发布环境下启用严格模式!严格模式会深度监测状态树来检测不合规的状态变更——请确保在发布环境下关闭严格模式,以避免性能损失。    // 具体后续enableStrictMode方法会提到    this.strict = strict    // init root module.    // this also recursively registers all sub-modules    // and collects all module getters inside this._wrappedGetters    // 这个作者的注释已经写得挺明白,就是初始化根模块,递归注册子模块,收集getter    installModule(this, state, [], this._modules.root)    // initialize the store vm, which is responsible for the reactivity    // (also registers _wrappedGetters as computed properties)    // 初始化store中的state,使得state变成响应式的,原理就是将state作为一个vue实例的data属性传入,具体在分析这个函数的时候会介绍    resetStoreVM(this, state)    // apply plugins    // 执行插件,这个是一个数组,所以遍历他,然后执行每个插件的函数    plugins.concat(devtoolPlugin).forEach(plugin => plugin(this))  }复制代码

呼呼呼~ 至此,终于把store类全部读完了,休息五分钟,然后继续往下看哈。

接下来关于state的获取和设置

// 获取state,  直接返回内部data的$$state  get state () {    return this._vm._data.$$state  }  set state (v) {    if (process.env.NODE_ENV !== 'production') {      assert(false, `Use store.replaceState() to explicit replace store state.`)    }  }复制代码

commit是vuex中一个比较重要的操作,因为它可以触发mutation修改对state的修改,并且是同步执行的

commit (_type, _payload, _options) {    // check object-style commit    // 首先统一传入参数的格式,主要是针对当type是个对象的情况,需要把这个对象解析出来    const {      type,      payload,      options    } = unifyObjectStyle(_type, _payload, _options)        // 缓存本次commit操作的类型和负荷,以供后续监听队列(this._subscribers)使用    const mutation = { type, payload }    // 获取相关的type的mutation函数,我们都知道,在vuex中都是通过commit一个类型然后触发相关的mutation函数来操作state的,所以在此必须获取相关的函数    const entry = this._mutations[type]    if (!entry) {      if (process.env.NODE_ENV !== 'production') {        console.error(`[vuex] unknown mutation type: ${type}`)      }      return    }    // 在_withCommit中触发上面获取的mutation函数,简单粗暴使用数组的forEach执行哈哈,之所以要在外面包一层_withCommit,是表明操作的同步性    this._withCommit(() => {      entry.forEach(function commitIterator (handler) {        handler(payload)      })    })    // 这个就是之前说的监听队列,在每次执行commit函数时都会遍历执行一下这个队列    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'      )    }  }复制代码

dispatch是跟commit有点相似的函数,但是commit必须是同步的,而dispatch是异步的,内部还是必须通过commit来操作state

dispatch (_type, _payload) {    // check object-style dispatch    // 同上面commit,不解释    const {      type,      payload    } = unifyObjectStyle(_type, _payload)        // 因为dispatch触发的是actions中的函数,所以这里获取actions相关函数,过程类似commit    const entry = this._actions[type]    if (!entry) {      if (process.env.NODE_ENV !== 'production') {        console.error(`[vuex] unknown action type: ${type}`)      }      return    }    // 因为dispatch支持异步,所以这里作者使用Promise.all来执行异步函数并且判断所有异步函数是否都已经执行完成,所以在文件最开始判断了当前环境必须支持promise就是这个原因    return entry.length > 1      ? Promise.all(entry.map(handler => handler(payload)))      : entry[0](payload)  }复制代码

subscribe函数,这是pub/sub模式在vuex中的一个运用,用户可以通过subscribe函数来监听state的变化,函数返回一个取消监听的函数,便于用户在合适的时机取消订阅

subscribe (fn) {    const subs = this._subscribers    if (subs.indexOf(fn) < 0) {      subs.push(fn)    }    // 返回取消订阅的函数,通过函数额splice方法,来清除函数中不需要的项    return () => {      const i = subs.indexOf(fn)      if (i > -1) {        subs.splice(i, 1)      }    }  }复制代码

watch函数,响应式地监测一个 getter 方法的返回值,当值改变时调用回调函数,原理其实就是利用vue中的watch方法

watch (getter, cb, options) {    if (process.env.NODE_ENV !== 'production') {      assert(typeof getter === 'function', `store.watch only accepts a function.`)    }    // 在上面构造函数中,我们看到this._watcherVM就是一个vue的实例,所以可以利用它的watch来实现vuex的watch,原理都一样,当监听的值或者函数的返回值发送改变的时候,就触发相应的回调函数,也就是我们传入的cb参数,options则可以来让监听立即执行&深度监听对象    return this._watcherVM.$watch(() => getter(this.state, this.getters), cb, options)  }复制代码

replaceState,根据名字就可知道,是替换当前的state

replaceState (state) {    this._withCommit(() => {      this._vm._data.$$state = state    })  }复制代码

registerModule函数,可以使用 store.registerModule 方法注册模块

registerModule (path, rawModule) {    if (typeof path === 'string') path = [path]    if (process.env.NODE_ENV !== 'production') {      assert(Array.isArray(path), `module path must be a string or an Array.`)      assert(path.length > 0, 'cannot register the root module by using registerModule.')    }    //其实内部时候通过,register方法,递归寻找路径,然后将新的模块注册root模块上,具体后续介绍到module的时候会详细分析    this._modules.register(path, rawModule)    //安装模块,因为每个模块都有他自身的getters,actions, modules等,所以,每次注册模块都必须把这些都注册上,后续介绍installModule的时候,会详细介绍到    installModule(this, this.state, path, this._modules.get(path))    // reset store to update getters...    // 重置VM    resetStoreVM(this, this.state)  }复制代码

unregisterModule函数,上述registerModule函数的相反操作,具体在module的时候会介绍到,在此了解个大概,先不纠结细节

unregisterModule (path) {    if (typeof path === 'string') path = [path]    if (process.env.NODE_ENV !== 'production') {      assert(Array.isArray(path), `module path must be a string or an Array.`)    }    this._modules.unregister(path)    this._withCommit(() => {      const parentState = getNestedState(this.state, path.slice(0, -1))      // 利用vue.delete方法,确保模块在被删除的时候,视图能监听到变化      Vue.delete(parentState, path[path.length - 1])    })    resetStore(this)  }复制代码

hotUpdate函数,Vuex 支持在开发过程中热重载 mutation、modules、actions、和getters

hotUpdate (newOptions) {    this._modules.update(newOptions)    resetStore(this, true)  }复制代码

_withCommit函数,从函数名可以看出这是一个内部方法,作用就是保证commit过程中执行的方法都是同步的

_withCommit (fn) {    // 保存原来的committing的状态    const committing = this._committing    //将想在的committing状态设置为true    this._committing = true    //执行函数    fn()    //将committing状态设置为原来的状态    this._committing = committing  }复制代码

到目前为止,我们已经看完了Store这个类的所有代码~慢慢消化,然后继续往下

----------------------------------------------------------这又是分割线----------------------------------------------------------------------------------------

接下来,我们分析一下,一些其他的辅助方法,跟上面store的一些内容会有相关。ready? Go

resetStore函数,用于重置整个vuex中的store,从代码中可以看出,这个函数主要的功能,就是将传入的store实例的_actions,_mutations,_wrappedGetters,_modulesNamespaceMap置为空,然后重新安装模块和重置VM,此方法在上述热更新和注销模块的时候会使用到

function resetStore (store, hot) {  store._actions = Object.create(null)  store._mutations = Object.create(null)  store._wrappedGetters = Object.create(null)  store._modulesNamespaceMap = Object.create(null)  const state = store.state  // init all modules  installModule(store, state, [], store._modules.root, true)  // reset vm  resetStoreVM(store, state, hot)}复制代码

resetStoreVM函数,这个用于重置store中的vm,所谓vm,指的就是视图模型,也就是常见mvvm中的vm,在此指的是将state作为data中$$state属性的一个vue实例

function resetStoreVM (store, state, hot) {   // 保存原有store的_vm  const oldVm = store._vm      // bind store public getters  store.getters = {}  // store的_wrappedGetters缓存了当前store中所有的getter  const wrappedGetters = store._wrappedGetters  const computed = {}  //遍历这个对象,获取每个getter的key和对应的方法  forEachValue(wrappedGetters, (fn, key) => {    // use computed to leverage its lazy-caching mechanism    // 将getter以key-value的形式缓存在变量computed中,其实后面就是将getter作为vue实例中的计算属性    computed[key] = () => fn(store)    // 当用户获取getter时,相当于获取vue实例中的计算属性,使用es5的这个Object.defineProperty方法做一层代理    Object.defineProperty(store.getters, key, {      get: () => store._vm[key],      enumerable: true // for local getters    })  })  // use a Vue instance to store the state tree  // suppress warnings just in case the user has added  // some funky global mixins  const silent = Vue.config.silent  // silent设置为true,则取消了所有的警告和日志,眼不见为净  Vue.config.silent = true    // 将传入的state,作为vue实例中的data的$$state属性,将刚刚使用computed变量搜集的getter,作为实例的计算属性,所以当state和getter都变成了响应式的了  store._vm = new Vue({    data: {      $$state: state    },    computed  })  Vue.config.silent = silent  // enable strict mode for new vm      if (store.strict) {    //如果设置了严格模式则,不允许用户在使用mutation以外的方式去修改state    enableStrictMode(store)  }  if (oldVm) {    if (hot) {      // dispatch changes in all subscribed watchers      // to force getter re-evaluation for hot reloading.      store._withCommit(() => {        // 将原有的vm中的state设置为空,所以原有的getter都会重新计算一遍,利用的就是vue中的响应式,getter作为computed属性,只有他的依赖改变了,才会重新计算,而现在把state设置为null,所以计算属性重新计算        oldVm._data.$$state = null      })    }    // 在下一次周期销毁实例    Vue.nextTick(() => oldVm.$destroy())  }}复制代码

installModule函数,用于安装模块,注册相应的mutation,action,getter和子模块等

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) {    // 将模块的state设置为响应式    const parentState = getNestedState(rootState, path.slice(0, -1))    const moduleName = path[path.length - 1]    store._withCommit(() => {      Vue.set(parentState, moduleName, module.state)    })  }  //设置本地上下文,主要是针对模块的命名空间,对dispatch,commit,getters和state进行修改,后面讲到makeLocalContext的时候会详细分析,现在只需要知道,这个操作让用户能够直接获取到对象子模块下的对象就可以了  const local = module.context = makeLocalContext(store, namespace, path)   //将mutation注册到模块上  module.forEachMutation((mutation, key) => {    const namespacedType = namespace + key    registerMutation(store, namespacedType, mutation, local)  })  //将action注册到模块上    module.forEachAction((action, key) => {    const namespacedType = namespace + key    registerAction(store, namespacedType, action, local)  })  //将getter注册到模块上  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)  })}复制代码

makeLocalContext函数,就是installModule中设置本地上下文的具体实现

function makeLocalContext (store, namespace, path) {   //如果没有命名空间,则是使用全局store上的属性,否则对store上的属性进行本地化处理  const noNamespace = namespace === ''  const local = {    dispatch: noNamespace ? store.dispatch : (_type, _payload, _options) => {      //dispatch的本地化处理,就是修改type      const args = unifyObjectStyle(_type, _payload, _options)      const { payload, options } = args      let { type } = args      if (!options || !options.root) {         //在type前面加上命名空间        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        }      }        //调用store上的dispatch方法      return store.dispatch(type, payload)    },    commit: noNamespace ? store.commit : (_type, _payload, _options) => {    // commit的本地化修改跟dispatch相似,也是只是修改了type,然后调用store上面的commit      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   //gettters和state的修改,则依赖于makeLocalGetters函数和getNestedState函数,后面会分析  Object.defineProperties(local, {    getters: {      get: noNamespace        ? () => store.getters        : () => makeLocalGetters(store, namespace)    },    state: {      get: () => getNestedState(store.state, path)    }  })  return local}复制代码

makeLocalGetters函数,则是对getter进行本地化处理

function makeLocalGetters (store, namespace) {  const gettersProxy = {}  const splitPos = namespace.length  Object.keys(store.getters).forEach(type => {    //这里获取的每个type都是一个有命名空间+本地type的字符串,例如: type的值可能为 “m1/m2/”+"typeName"    // 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.    //相当于做了一层代理,将子模块的localType映射到store上的type    Object.defineProperty(gettersProxy, localType, {      get: () => store.getters[type],      enumerable: true    })  })  return gettersProxy}复制代码

registerMutation函数,就是注册mutation的过程,将相应type的mutation推到store._mutations[type]的队列中,当commit这个type的时候就触发执行队列中的函数

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

registerAction函数,注册action的过程,原理类似于registerMutation,不同点在于action支持异步,所以必须用promise进行包装

function registerAction (store, type, handler, local) {  const entry = store._actions[type] || (store._actions[type] = [])  entry.push(function wrappedActionHandler (payload, cb) {    let res = handler({      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    }  })}复制代码

registerGetters函数,根据type,将getter方法挂载在store._wrappedGetters[type]下面

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) {    // 为子模块的getter提供了这个四个参数,方便用户获取,如果是根模块,则local跟store取出来的state和getters相同    return rawGetter(      local.state, // local state      local.getters, // local getters      store.state, // root state      store.getters // root getters    )  }}复制代码

enableStrictMode函数则是在严格模式下,不允许state被除mutation之外的其他操作修改,代码比较简单,利用vue的$watch方法实现的

function enableStrictMode (store) {  store._vm.$watch(function () { return this._data.$$state }, () => {    if (process.env.NODE_ENV !== 'production') {      assert(store._committing, `Do not mutate vuex store state outside mutation handlers.`)    }  }, { deep: true, sync: true })}复制代码

getNestedState函数,获取对应路径下的state

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

unifyObjectStyle函数,作用是调整参数,主要是当type是一个对象的时候,对参数进行调整

function unifyObjectStyle (type, payload, options) {  if (isObject(type) && type.type) {    options = payload    payload = type    type = type.type  }  if (process.env.NODE_ENV !== 'production') {    assert(typeof type === 'string', `Expects string as the type, but found ${typeof type}.`)  }  return { type, payload, options }}复制代码

以上是相关辅助函数的全部内容,你看明白了么~

----------------------------------------------------------这依然是分割线------------------------------------------------------------------------------------

文件的最后,就是定义了install函数,然后自动执行了这个函数,让vuex能够在项目中运作起来

export function install (_Vue) {  if (Vue) {    if (process.env.NODE_ENV !== 'production') {      console.error(        '[vuex] already installed. Vue.use(Vuex) should be called only once.'      )    }    return  }  Vue = _Vue  //在vue的生命周期中初始化vuex,具体实现后面讲到mixin.js这个文件时会说明  applyMixin(Vue)}// auto install in dist modeif (typeof window !== 'undefined' && window.Vue) {  install(window.Vue)}复制代码

以上就是store.js的所有内容啦~

----------------------------------------------------------严肃分割线------------------------------------------------------------------------------------

接下来讲解有关module目录下的内容,该目录有两个文件分别是module-collection.js和module.js,这两个文件主要是有关于vuex中模块的内容;

首先我们看看module-collection.js,这个文件主要导出一个ModuleCollection类:
构造函数

constructor (rawRootModule) {    // register root module (Vuex.Store options)    //主要是注册根模块,我们在之前store的构造函数中曾经使用到 this._modules = new ModuleCollection(options),注册一个根模块然后缓存在this._module中    this.register([], rawRootModule, false)  }复制代码

紧接着看看下面register函数,它用于注册模块

register (path, rawModule, runtime = true) {    if (process.env.NODE_ENV !== 'production') {      assertRawModule(path, rawModule)    }    // 创建一个新模块,具体会在后面讲到Module的时候分析    const newModule = new Module(rawModule, runtime)    // 判读是否为根模块    if (path.length === 0) {      this.root = newModule    } else {      //根据path路径,利用get方法获取父模块        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)      })    }  }复制代码

相反,unregister函数则是移除一个模块

unregister (path) {    // 通过get方法获取父模块    const parent = this.get(path.slice(0, -1))    //获取需要删除的模块的名称,即他的key    const key = path[path.length - 1]    if (!parent.getChild(key).runtime) return    //利用module中removeChild方法删除该模块,其实就是delete了对象上的一个key    parent.removeChild(key)  }复制代码

get函数,其实就是利用es5中数组reduce方法,从根模块开始根据传入的path来获取相应的子模块

get (path) {    return path.reduce((module, key) => {      return module.getChild(key)    }, this.root)  }复制代码

getNamespace函数,利用传入的参数path,生成相应的命名空间,实现的原理跟上述的get方法类似

getNamespace (path) {    let module = this.root    return path.reduce((namespace, key) => {      module = module.getChild(key)      return namespace + (module.namespaced ? key + '/' : '')    }, '')  }复制代码

upate方法,就是更新模块,具体看下面update方法的实现

update (rawRootModule) {    update([], this.root, rawRootModule)  }复制代码

以上就是整个ModuleCollection类的实现

接下来讲解一下function update的实现

function update (path, targetModule, newModule) {  if (process.env.NODE_ENV !== 'production') {    assertRawModule(path, newModule)  }  // update target module  //目标模块更新为新模块,具体实现是将原有模块的namespaced,actions,mutations,getters替换为新模块的namespaced,actions,mutations,getters  // 具体会在Module类中update方法讲解  targetModule.update(newModule)  // update nested modules  // 如果新的模块有子模块,则递归更新子模块  if (newModule.modules) {    for (const key in newModule.modules) {      if (!targetModule.getChild(key)) {        if (process.env.NODE_ENV !== 'production') {          console.warn(            `[vuex] trying to add a new module '${key}' on hot reloading, ` +            'manual reload is needed'          )        }        return      }      update(        path.concat(key),        targetModule.getChild(key),        newModule.modules[key]      )    }  }}复制代码

至于assertRawModule方法和makeAssertionMessage方法,就是一些简单的校验和提示,不影响主流程&代码比较简单,这里不做赘述

以上就是整个module-collection.js文件的所有内容

接下来就应该分析目录中的另一个文件module.js,这个文件主要导出一个Module类,这个类主要描述了vuex中模块的功能

构造函数,主要做了一些模块初始化的事情

//构造函数,主要做了一些模块初始化的事情  constructor (rawModule, runtime) {    //缓存运行时的标志    this.runtime = runtime    //创建一个空对象来保存子模块    this._children = Object.create(null)    //缓存传入的模块    this._rawModule = rawModule    //缓存传入模块的state,如果state是一个函数,则执行这个函数    const rawState = rawModule.state    this.state = (typeof rawState === 'function' ? rawState() : rawState) || {}  }复制代码

namespaced函数是主要就是获取当前模块是否是命名模块,vuex支持命名模块和匿名模块

get namespaced () {    return !!this._rawModule.namespaced  }复制代码

addChild,removeChild,getChild这三个函数就分别是添加,删除,获取子模块,内容比较简单,不赘述

update方法,将原有缓存模块的namespaced,actions,mutations,getters替换成新传入模块的namespaced,actions,mutations,getters

update (rawModule) {    this._rawModule.namespaced = rawModule.namespaced    if (rawModule.actions) {      this._rawModule.actions = rawModule.actions    }    if (rawModule.mutations) {      this._rawModule.mutations = rawModule.mutations    }    if (rawModule.getters) {      this._rawModule.getters = rawModule.getters    }  }复制代码

forEachChild函数,利用util中forEachValue方法,变量每个子模块,将每个子模块作为传入的回调函数参数,然后执行回调函数

forEachChild (fn) {    forEachValue(this._children, fn)  }复制代码

forEachGetter,forEachAction,forEachMutation代码逻辑跟上述forEachChild十分类似,不在赘述

以上就是module.js文件的所有内容,至此我们也已经全部分析完module目录下的所有代码了

---------------------------------------------------一本正经分割线--------------------------------------------------------------------------------

接下来,我们再看看help.js这个文件,这个文件主要是提供了一些帮助性的方法,使得用户在使用vuex的过程中体验更好,更加方便

首先我们先看看文件后面三个函数:normalizeMap,normalizeNamespace,getModuleByNamespace

normalizeMap函数,这个方法的作用是格式化传入的对象

function normalizeMap (map) {  // 如果传入的对象是数组,则放回一个每一项都是key-val对象的数组,其中key和val的值相同  // 如果出入的是一个对象,则变量这个对象,放回一个每一项都是key-val数组,其中key对应对象的key,val对应相应key的值  return Array.isArray(map)    ? map.map(key => ({ key, val: key }))    : Object.keys(map).map(key => ({ key, val: map[key] }))}复制代码

normalizeNamespace函数,调整参数,格式化命名空间

function normalizeNamespace (fn) {  return (namespace, map) => {    //如果没传入命名空间,或者传入的命名空间不是一个字符串,则丢弃该参数    if (typeof namespace !== 'string') {      map = namespace      namespace = ''    } else if (namespace.charAt(namespace.length - 1) !== '/') {      //否则判断命名空间后面是否有加上‘/’,如果没有则加上      namespace += '/'    }    //最后执行传入的回调函数    return fn(namespace, map)  }}复制代码

getModuleByNamespace函数,通过命名空间来获取模块

function getModuleByNamespace (store, helper, namespace) {  // 返回store._modulesNamespaceMap缓存的模块  const module = store._modulesNamespaceMap[namespace]  if (process.env.NODE_ENV !== 'production' && !module) {    console.error(`[vuex] module namespace not found in ${helper}(): ${namespace}`)  }  return module}复制代码

mapState函数,我们可以通过这个方法将state解构到vue项目中去,使其变成vue实例中的计算属性

export const mapState = normalizeNamespace((namespace, states) => {  //定义一个空对象  const res = {}  normalizeMap(states).forEach(({ key, val }) => {    //收集states的所有key,对应key的值,改变成一个mappedState方法,符合计算属性的特点    res[key] = function mappedState () {      //获取store的state和getters      let state = this.$store.state      let getters = this.$store.getters      //如果存在命名空间,则将命名空间下子模块的state和getters覆盖原来store的state和getters      if (namespace) {        const module = getModuleByNamespace(this.$store, 'mapState', namespace)        if (!module) {          return        }        state = module.context.state        getters = module.context.getters      }      //如果对应的val是函数则执行,否则返回state下的值      return typeof val === 'function'        ? val.call(this, state, getters)        : state[val]    }    // mark vuex getter for devtools    res[key].vuex = true  })  //返回这个包装过state的对象,这个对象可以结构成vue中的计算属性  return res})复制代码

mapMutations函数,则是将mutation解构到vue实例中的methods中,使得用户可以直接调用methods中的方法来执行store.commit

export const mapMutations = normalizeNamespace((namespace, mutations) => {  //定义一个空对象  const res = {}  normalizeMap(mutations).forEach(({ key, val }) => {    val = namespace + val    res[key] = function mappedMutation (...args) {      if (namespace && !getModuleByNamespace(this.$store, 'mapMutations', namespace)) {        return      }      //调用了store中的commit方法,触发相应的mutation函数的执行      return this.$store.commit.apply(this.$store, [val].concat(args))    }  })  return res})复制代码

mapGetters的逻辑跟mapState类似,mapActions的逻辑跟mapMutations类似,这里不再赘述

自此,我们把help.js的内容也分析完了

转载地址:http://cziga.baihongyu.com/

你可能感兴趣的文章
Centos安装KDE或GNOME
查看>>
Eclipse & IDEA 中常用的快捷键
查看>>
javascript ---IPhone滑动解锁
查看>>
table固定行和表头
查看>>
<每天读一点职场心理学>读书笔记
查看>>
android 判断SIM卡是哪个运营商
查看>>
删除N天前的M(天)个目录 、删除N天前最后修改的文件 ForFiles, dos command 批处理命令cmd/bat...
查看>>
[转载]Visual Studio 2010敏捷利剑:详解Scrum
查看>>
Java Collection: List、Set、 Map、 HashMap、 Hashtable、 Vector
查看>>
T-SQL查询进阶--流程控制语句
查看>>
备份Toad中保存的数据库连接用户名和密码
查看>>
ASP.NET中 Repeater 的使用前台绑定
查看>>
微信公众平台模拟群发技术
查看>>
C语言学习之指针详解
查看>>
学习使用Bing Maps Silverlight Control(一):准备和新建
查看>>
什么是Scrum
查看>>
nginx负载均衡的5种策略
查看>>
90%人都不知道:SVN 和 Git 的一些误解和真相
查看>>
防火墙配置十大任务之九,验证防火墙的运行
查看>>
【linux】浅谈Linux下的 find 指令
查看>>