vuex的深入学习[基于vuex3]----篇(二)

store对象的创建

store的传递图
在这里插入图片描述

在这里插入图片描述

创建语句索引

  1. 创建vuex的语句为new Vuex.Store({…})
  2. Vuex的入口文件是index.js,store是index.js导出的store类
  3. store类是store.js文件中定义的。

Store的构造函数constructor

  1. 判断vuex是否被注入,就是将vue挂载在window对象上,自动检测window.Vue,如果有挂载,而且没有被注册过,则调用注册方法
if (!Vue && typeof window !== 'undefined' && window.Vue) {
      install(window.Vue)
}
  1. 断言
    断言是否被注册,是否支持promise,断言类有没有被正确的实例化,这些断言语句,在vue build出来以后,报错信息会被忽略
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.`)
}
  • 支持promise语法
  • 已经执行安装函数进行装载
  • 在webpack配置的时候,npm build会将process.env.NODE_ENV 设置为true
function assert(condition, msg) {
    if(!condition) throw new Error(`[vuex] ${msg}`)
}
  1. 部分重要的容器
this._committing = false // 表示状态标识,在严格模式下,防止非commit操作下,state被修改
this._actions = Object.create(null) // action函数的数组的对象,保存所有action回调函数。从null中创建对象,object.create(null)没有继承任何原型方法,也就是说他的原型链没有上一层,从而定义纯粹的对象
this._actionSubscribers = [] // 订阅action操作的函数数组,里面的每个函数,将在action函数被调用之间被调用,该功能类似于插件,和主功能无关。
this._mutations = Object.create(null) // mutation函数的数组的对象,保存所有的mutations回调函数
this._wrappedGetters = Object.create(null) //保存getter函数的函数数组对象容器
this._modules = new ModuleCollection(options) // 解析并生成模块树,通过树结构,保存配置文件内容
this._modulesNamespaceMap = Object.create(null) // 保存命名空间的模块对象,以便在辅助函数createNamespacedHelpers中快速定位到待命名空间的模块。
this._subscribers = []    // 订阅mutation操作的函数数组,里面的每个函数,将在commit执行后被调用,类似于插件,和主功能无关。
this._watcherVM = new Vue() // 定义一个vue对象,vue类将在调用vuex安装函数,install 的时候,被传递进来。

简洁版

this._committing = false // 是否在进行提交状态标识
this._actions = Object.create(null) // acitons操作对象
this._mutations = Object.create(null) // mutations操作对象
this._wrappedGetters = Object.create(null) // 封装后的getters集合对象
this._modules = new ModuleCollection(options) // Vuex支持store分模块传入,存储分析后的modules
this._modulesNamespaceMap = Object.create(null) // 模块命名空间map
this._subscribers = [] // 订阅函数集合,Vuex提供了subscribe功能
this._watcherVM = new Vue() // Vue组件用于watch监视变化

ModuleCollection

this._modules = new ModuleCollection(options)

ModuleCollection函数主要是讲传入的options对象构造为一个module对象,并循环调用(this.register[key], rawModul, false)为其中的modules属性进行模块注册,使得其称为module对象后,最后options对象被构成一个完整的组件树,ModuleCollection类还提供了modules的更替功能。

constructor
export default class ModuleCollection {
    constructor(rawRootModule) {
        this.register([], rawRootModule, false)
    }
}
  • 形参: rawRootModule,在store中new ModuleCollection(options)传入,而options是store的构造函数参数,就是new Vuex.Store({…})传入的参数
{
    state, 
    getters,
    actions,
    mutations  
}
register
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)
    }
    if (rawModule.modules) {
        forEachValue(rawModule.modules, (rawChildModule, key) => {
        this.register(path.concat(key), rawChildModule, runtime)
        })
    }
}
参数解析
  • path是注册模块的层级关系数组,保存当前模块及各个祖先模块的名字。[a, b, c, 当前模块] => {modules: {a, modules: {b, module:{当前模块} }}}
  • rawModule是模块的配置参数,在定义模块的时候,开发者定义的模块配置内容,比如state, getter, actions等等
  • runtime标识是否是运行状态,在运行状态下,不能进行某些特定的操作。
assertRawModule
if (process.env.NODE_ENV !== 'production') {
      assertRawModule(path, rawModule)
}
  • 对模块的配置信息进行断言
  • 判断了getters,mtations, actions等配置的数据类型是否正确。
const newModule = new Module(rawModule, runtime),找到module.js文件找到其构造函数
  constructor (rawModule, runtime) {
    this.runtime = runtime
    this._children = Object.create(null) // 定义子模块对象容器
    this._rawModule = rawModule // 保存配置参数本身
    const rawState = rawModule.state // 从配置参数中获得了state这个参数
    this.state = (typeof rawState === 'function' ? rawState() : rawState) || {} // 解析state,如果是函数,则运用工厂模式产生配置对象
  }
根据path模块判断当前是否为根模块
  1. 如果是根模块,则通过this.root进行保存
  2. 如果不是根模块
    • get方法传入的是路径数组,slice(0,-1)是为了去除本模块名,保留所有祖先模块的路径,从而获取夫模块。
    • get函数内部,使用了数组的reduce方法
    get (path) {
        return path.reduce((module, key) => {
          return module.getChild(key)
    }, this.root)
    }
    
      getChild (key) {
        return this._children[key]
      }
      addChild (key, module) {
        this._children[key] = module
      }
    
if(rawModule.modules)判断模块配置信息中,有没有子模块的配置

如果有,则递归调用注册函数register本身,传入的参数加上了子模块名的path模块层级数组,rawchildmodule是子模块配置文件,runtime是继承而来的运行标识符

获取dispatch和commit函数,并复写该函数

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)
}

复写的作用是将两个函数的this值绑定到vuex实例本身上,防止因为this的指向修改而被修改
这两个函数可以通过mapMutationsmapActions辅助函数转换为Vue中的普通函数,这时this指向Vue组件,而不是Vuex实例

后续代码

this.strict = strict
const state = this._modules.root.state //根据根模块的state变量索引
installModule(this, state, [], this._modules.root)//安装根模块,设置commit,dispatch函数的重载,根据是否设置命名空间,设置参数前缀,将getter,commit,action放到相对应的容器中保存起来。
resetStoreVM(this, state)// 借助vue的watch功能和computed功能,实现数据的响应式
plugins.forEach(plugin => plugin(this)) // 插件的注册,和主功能无关
const useDevtools = options.devtools !== undefined ? options.devtools : Vue.config.devtools
if (useDevtools) { // vue调试工具的处理
    devtoolPlugin(this)
}
installModule(模块安装函数)
参数介绍
  • store: store对象实例,new Vuex.store({…})生成的对象
  • rootState: 根模块的state对象
  • path: 当前模块所处的层级数组
  • module: 模块对象
  • hot
实例代码
  function installModule (store, rootState, path, module, hot) {
  const isRoot = !path.length // 判断当前模块是否为根模块
  const namespace = store._modules.getNamespace(path) //  获取模块的命名空间 在module.js中有个函数
  if (module.namespaced) {
    store._modulesNamespaceMap[namespace] = module
  }
  if (!isRoot && !hot) { //非根目录
    const parentState = getNestedState(rootState, path.slice(0, -1))  //寻找父结点的state,形成一个和module树对应的state树
    const moduleName = path[path.length - 1]
    store._withCommit(() => { //允许在函数执行的时候修改state的值
      Vue.set(parentState, moduleName, module.state) //往父State对象中,添加以模块名为key,为模块内state对象为value的变量,借用Vue.set方法,实现数据响应
    })
  }
    const local = module.context = makeLocalContext(store, namespace, path)  
      module.forEachMutation((mutation, key) => {  //对模块的mutation进行注册
        const namespacedType = namespace + key
        registerMutation(store, namespacedType, mutation, local)
      })
    
      module.forEachAction((action, key) => {  //对模块的action进行注册
        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)
      })
}
// 获取子模块的state变量的时候,要通过this.$store.state.moduleName.xxx

补充
getNamespace

//从跟模块中出发,根据给出的路径数组,递归每一个层级的模块
getNamespace(path) {
    let module = this.root
    return path.reduce((namespace, key) => {
        module = module.getChild(key)
        return namespace + (module.namespaced ? key + "/" : '')
    }, '')
}
{
  moduleA:{
    namespaced:true,
    modules:{
      moduleB:{
          namespaced:false,
          modules:{
            moduleC:{
              namespaced:true,
            }
          }
        }
    }
  }
}
makeLocalContext创建模块内容

在这里插入图片描述

function makeLocalContext (store, namespace, path) {
  const noNamespace = namespace === ''
  const local = {
    dispatch: noNamespace ? store.dispatch : (_type, _payload, _options) => {  // 根据namespace进行判断,本模块或祖先模块中是否有命名空间,没有设置直接使用store中的dispatch函数。
      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) //不是发送全局的action函数,即只发送本模块内的action函数·调用store实例的dispatch函数
    },
    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)
    }
  }
  Object.defineProperties(local, {
//设置拦截器,如果有namespace,就直接访问store对象实例中生成的getters变量,如果有就生成自己的getters
    getters: {
      get: noNamespace
        ? () => store.getters
        : () => makeLocalGetters(store, namespace)
    },
    state: {
      get: () => getNestedState(store.state, path) //state的获取,则不需要命名空间前缀的识别,而且直接通过getNestedState,从state树结构中取
    }
  })

  return local
}
makeLocalGetters(store, namespace)
function makeLocalGetters (store, namespace) {
  const gettersProxy = {}
  const splitPos = namespace.length
  Object.keys(store.getters).forEach(type => {
    if (type.slice(0, splitPos) !== namespace) return //检测各个函数名,判断该函数名的前缀是否与模块的命名空间前缀相同。找到即符合命名前缀的函数。
    const localType = type.slice(splitPos) //去除命名空间前缀,能获取纯粹的getter函数名
    Object.defineProperty(gettersProxy, localType, {
      get: () => store.getters[type],
      enumerable: true
    })
  })
  return gettersProxy //其实所有的getter都存放在了Store实例的_wrappedGetters变量中,如模块没有命名空间前缀,则直接存入。否则将getter函数名带上命名空间前缀后,再加入进去
}

state是独立的状态树,有明显的层级关系.getters则全部都放在store实例的getters变量中保存,属于同层级,只是通过函数前缀进行了区分(有设命名空间的话)

  • 30
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值