你想要的 —— Vuex源码分析「终于解决」

你想要的 —— Vuex源码分析「终于解决」最详细的vuex源码分析 提升js能力的干货

你想要的——vuex源码分析



大家好,今天给大家带来的是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源码分析「终于解决」

从目录结构可以看出,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等

你想要的 —— Vuex源码分析「终于解决」

先看看构造函数吧:



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 mode
if (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的内容也分析完了

—————————————————一本正经分割线——————————————————————————–

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
转载请注明出处: https://daima100.com/12975.html

(0)

相关推荐

  • 快速计算幂函数- Python e幂函数实现

    快速计算幂函数- Python e幂函数实现幂函数是指以自变量为底数,以某个常数为指数的函数。

    2023-12-21
    102
  • cannot convert value of type_incorrect date value

    cannot convert value of type_incorrect date value在Mysql数据库中使用DATETIME类型来存储时间,使用JDBC中读取这个字段的时候,应该使用 ResultSet.getTimestamp(),这样会得到一个java.sql.Timestam…

    2023-03-19
    156
  • 腾讯云elasticsearch_腾讯云的数据库怎么那么慢

    腾讯云elasticsearch_腾讯云的数据库怎么那么慢近十年来,中国基础软件发展势头迅猛,市场前景看高,越来越多的企业也正在进行基础软件升级。那中国基础软件行业目前在国际市场上有什么优势,面临哪些困境,以及未来基础软件行业会如何发展呢?腾讯云数据库邀请沙

    2023-05-22
    141
  • IE浏览器曝高危漏洞 专家提三条防范建议「终于解决」

    IE浏览器曝高危漏洞 专家提三条防范建议「终于解决」新华网天津4月30日电(记者 周润健)近日微软发布安全公告称,IE浏览器所有版本均存在一处高危漏洞,可被木马病毒利用潜入IE用户电脑。在微软官方

    2023-07-10
    137
  • 云数据库介绍_云数据中心架构

    云数据库介绍_云数据中心架构近期,ArchSummit 全球架构师峰会(以下简称:AS峰会)北京站圆满落幕。AS峰会是极客邦科技旗下 InfoQ 中国团队推出的重点面向高端技术管理者、架构师的技术会议。AS峰会北京站以“升级架构

    2023-06-03
    145
  • 什么是爬虫

    什么是爬虫在互联网时代,人们通过浏览器访问网站获取所需信息已成为日常生活不可或缺的一部分。但是,当人们需要通过大量的网站或特定的网站来获取信息时,这将变得非常困难和费时,这时候,爬虫就应运而生了。

    2024-05-22
    63
  • Python中的 % 用法解析

    Python中的 % 用法解析Python是一门非常流行的高级编程语言,它提供了很多方便的特性帮助开发者完成各种任务。其中,在Python中用于字符串格式化的%s操作符是一个广泛应用的特性;它允许开发者按照特定格式插入变量值。

    2024-08-23
    23
  • c# hadoop_hadoop 命令

    c# hadoop_hadoop 命令Hive Hive将HiveQL(类sql语言)转为MapReduce,完成数据的查询与分析,减少了编写MapReduce的复杂度。它有以下优点: 学习成本低:熟悉sql就能使用 良好的数据分析:底层

    2023-05-14
    164

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注