IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> JavaScript知识库 -> Vuex 源码解析 -> 正文阅读

[JavaScript知识库]Vuex 源码解析

本人阅读vuex源码主要想了解的问题

  • vuex的单项数据流的调用流程是什么?
  • 如何与vue结合实现数据改变后的响应式更新?
    • 在vue3版本和vue2.x版本的区别是什么?

如下基于vuex 4.1.0版本

管理配置项

  • 配置项生成 module 类

传递给vuex的配置项目会生成一个module类进行管理,如果配置项中存在modules则会继续生成对应的module,整个过程是通过递归处理的

import { forEachValue } from '../util'

// Base data struct for store's module, package with some attribute and method
export default class Module {
  // rawModule就是开发者定义的配置项
  constructor (rawModule, runtime) {
    this.runtime = runtime
    // Store some children item
    // 如果存在modules,则会创建对应的子module挂载打到父module的_children属性上
    this._children = Object.create(null)
    // Store the origin module object which passed by programmer
    // 存储原始配置、state
    this._rawModule = rawModule
    const rawState = rawModule.state

    // Store the origin module's state
    // 兼容state为对象或函数的格式
    this.state = (typeof rawState === 'function' ? rawState() : rawState) || {}
  }
  // 判断配置项中是否配置了namespaced
  get namespaced () {
    return !!this._rawModule.namespaced
  }
 // 添加子module,挂载到当前module的_children属性上
  addChild (key, module) {
    this._children[key] = module
  }

  removeChild (key) {
    delete this._children[key]
  }

  getChild (key) {
    return this._children[key]
  }

  hasChild (key) {
    return key in this._children
  }
  // 根据最新配置项信息更新module,在热更新的时候使用
  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
    }
  }
  
  // 如下都是module的辅助方法,调用时得到对象的value、key
  forEachChild (fn) {
    forEachValue(this._children, fn)
  }

  forEachGetter (fn) {
    if (this._rawModule.getters) {
      forEachValue(this._rawModule.getters, fn)
    }
  }

  forEachAction (fn) {
    if (this._rawModule.actions) {
      forEachValue(this._rawModule.actions, fn)
    }
  }

  forEachMutation (fn) {
    if (this._rawModule.mutations) {
      forEachValue(this._rawModule.mutations, fn)
    }
  }
}

  • module-collection管理所有module

根据传递的配置项,生成对应的module,如果有modules,则会递归处理生成对应的子module

该类用来注册、管理所有的module

import Module from './module'
import { assert, forEachValue } from '../util'

export default class ModuleCollection {
  constructor (rawRootModule) {
    // register root module (Vuex.Store options)
    this.register([], rawRootModule, false)
  }
  // path是要获取的module路径,返回指定的module,['a','b','c']
  get (path) {
    return path.reduce((module, key) => {
      return module.getChild(key)
    }, this.root)
  }
  // 根据path(每个module对象注册时的key,获取该module的路径)
  getNamespace (path) {
    let module = this.root
    return path.reduce((namespace, key) => {
      module = module.getChild(key)
      return namespace + (module.namespaced ? key + '/' : '')
    }, '')
  }
  // 热更新相关,会根据最新module配置,重新初始化store
  update (rawRootModule) {
    update([], this.root, rawRootModule)
  }
  // 递归注册每一个module
  register (path, rawModule, runtime = true) {
    // dev环境下进行类型断言,判断module选项是否正确(getters、mutations、actions)
    if (__DEV__) {
      assertRawModule(path, rawModule)
    }
    // 管理module,rawModule即为传递给createStore的module选项
    const newModule = new Module(rawModule, runtime)
    // 首次创建module
    if (path.length === 0) {
      this.root = newModule
    } else {
      // path.slice(0, -1)返回除了最后一位的结果
      // this.get获取当前路径的父module
      const parent = this.get(path.slice(0, -1))
      // 将新的module添加进整个store对象中,挂在父module下的_children属性下
      // 多个module路径会被转化为['x','xx',...]格式,根据最后一位注册module,即使用时开发者写好的名称{...,modules:{a:...,b:...}}
      parent.addChild(path[path.length - 1], newModule)
    }

    // register nested modules
    // 如果传递了modules,递归挂载,会走上面的parent.addChild逻辑
    if (rawModule.modules) {
      forEachValue(rawModule.modules, (rawChildModule, key) => {
        this.register(path.concat(key), rawChildModule, runtime)
      })
    }
  }

  // 卸载module
  unregister (path) {
    // 获取当前路径的父module
    const parent = this.get(path.slice(0, -1))
    const key = path[path.length - 1]
    const child = parent.getChild(key)

    if (!child) {
      if (__DEV__) {
        console.warn(
          `[vuex] trying to unregister module '${key}', which is ` +
          `not registered`
        )
      }
      return
    }

    if (!child.runtime) {
      return
    }
    // 从父的 this._children 对象上删掉该 key
    parent.removeChild(key)
  }

  isRegistered (path) {
    const parent = this.get(path.slice(0, -1))
    const key = path[path.length - 1]

    if (parent) {
      return parent.hasChild(key)
    }

    return false
  }
}

注册流程

  • 注册module

使用者调用createStore时会实例化store,store便是可以直接交给外部调用的

export function createStore (options) {
  return new Store(options)
}

Store

import ModuleCollection from './module/module-collection'

export class Store {
  constructor (options = {}) {
    if (__DEV__) {
      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,
      devtools
    } = options

    // ...
    // 给传递的选项中的每一个module创建module类进行管理,如果选项存在modules属性,会递归创建并挂在父module的_children属性下
    this._modules = new ModuleCollection(options)
    // 每个module的命名路径与module的映射表:{ 'a/b/moduleA/' : moduleA }    
    // ...
  }
}

总结一下:到这里可以知道,整个交给vuex的内容会被转换成一个个module,根据层级关系进行挂载,整个Vuex是一个大的对象

  • 注册state、mutations、getters、actions

这里先总结一下后文经常出现的变量:

  • path:保存module命名路径的数组,比如b模块:[‘a’,‘b’]
  • namespace:保存模块的命名空间路径,是个字符串,比如b模块:‘a/b/’
    /*
    * {
    * 	modules:{
    * 		a:{
    * 			namespaced: true,
    * 			...
    * 			modules:{
    * 				b:{
    * 					namespaced: true,
    * 					...
    * 				}
    * 			}
    * 		}
    * 	}
    * }
    * 
    * 
    */

export class Store {
  constructor (options = {}) {
    if (__DEV__) {
      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,
      devtools
    } = options

    // store internal state
    // 标识是否是直接修改state,严格模式下会根据该标识进行报错处理
    this._committing = false
    // 保存所有的actions、mutations
    this._actions = Object.create(null)
    // ...
    this._mutations = Object.create(null)
    // 保存所有的getters
    this._wrappedGetters = Object.create(null)
    // 保存所有getters的命名空间,主要是在各个module中只传递getter的key时,会自动拼接上命名空间前缀('a/b/fn1')
    // 模块b的命名空间为:'a/b/'
    this._makeLocalGettersCache = Object.create(null)
    // 每个module的命名空间与module的映射表:{ 'a/b/moduleA/' : moduleA }
    this._modulesNamespaceMap = Object.create(null)

    // 给传递的选项中的每一个module创建module类进行管理,如果选项存在modules属性,会递归创建并挂在父module的_children属性下
    this._modules = new ModuleCollection(options)
	
	//...
		
    // 获取 根module 的state
    const state = this._modules.root.state

    // init root module.
    // this also recursively registers all sub-modules
    // and collects all module getters inside this._wrappedGetters
    // 初始化所有module,收集所有module的getters放入this._wrappedGetters
    installModule(this, state, [], this._modules.root)
	
	//...
  }

installModule

  • 递归注册所有配置项
    • 将所有module的state统一保存在根module的state上,通过module的name进行区分
    • 注册mutations等方法
    • 为所有声明了namespaced: true的module创建一个局部下文,通过这个局部上下文调用module的内容(actions等),会自动拼接上该方法等命名空间路径
export function installModule (store, rootState, path, module, hot) {
  const isRoot = !path.length
  // 获取命名空间,格式为:x/xx/,选项没有设置namespaced则返回''
  // 所以格式可能有四种情况: x/xx/、 x/xx/'' 、 x/xx/''/xxx/ 、''
  // 注意上面的''只是进行说明,实际拼接''不会改变字符串,则上面实际上为:x/xx/、 x/xx/ 、 x/xx/xxx/ 、空字符串
  const namespace = store._modules.getNamespace(path)

  // register in namespace map
  // 如果声明了 namespaced 属性
  if (module.namespaced) {
    // 校验是否有重复声明的module
    if (store._modulesNamespaceMap[namespace] && __DEV__) {
      console.error(`[vuex] duplicate namespace ${namespace} for the namespaced module ${path.join('/')}`)
    }
    // 保存模块的命名空间
    store._modulesNamespaceMap[namespace] = module
  }

  // set state
  // 根据module的key添加新module的state到父state下,以及校验module的key是否和父module state某个属性相同
  if (!isRoot && !hot) {
    // 获取给定path下的state,path.slice(0, -1)返回父module的路径
    const parentState = getNestedState(rootState, path.slice(0, -1))
    const moduleName = path[path.length - 1]
    // 更改state必须调用_withCommit方法,通过mutation调用进行更改最终也是调用_withCommit方法
    store._withCommit(() => {
      if (__DEV__) {
        // 校验添加的module key是否和父state中的某个属性key相同,相同会覆盖掉
        if (moduleName in parentState) {
          console.warn(
            `[vuex] state field "${moduleName}" was overridden by a module with the same name at "${path.join('.')}"`
          )
        }
      }
      // 将新module的state,根据module对象的key,添加进父state对象中
      parentState[moduleName] = module.state
    })
  }

  // 为module创建一个局部上下文,用于当前module调用action等时,不用手动添加上级module的路径前缀
  const local = module.context = makeLocalContext(store, namespace, path)

  module.forEachMutation((mutation, key) => {
    // key 是mutation定义时的命名
    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)
  })
  
  // 如果存在modules,通过该方式进行递归注册
  module.forEachChild((child, key) => {
    installModule(store, rootState, path.concat(key), child, hot)
  })
}

getNestedState

  • 所有module的state也是保存在一个对象中,通过module的name进行挂载,{…,moduleA:{…,moduleB:…}}
export function getNestedState (state, path) {
  return path.reduce((state, key) => state[key], state)
}

注册mutations、actions等配置

  module.forEachMutation((mutation, key) => {
    // key 是mutation定义时的命名
    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)
  })
  
  // 如果存在modules,通过该方式进行递归注册
  module.forEachChild((child, key) => {
    installModule(store, rootState, path.concat(key), child, hot)
  })

  //... 

// 注册mutation
function registerMutation (store, type, handler, local) {
  const entry = store._mutations[type] || (store._mutations[type] = [])
  // 这里是push,意味着相同type的mutaion会一并触发,并不会覆盖
  entry.push(function wrappedMutationHandler (payload) {
    handler.call(store, local.state, payload)
  })
}

// 注册action,调用结果使用Promise.resolve包装
function registerAction (store, type, handler, local) {
  const entry = store._actions[type] || (store._actions[type] = [])
  // 这里是action,意味着相同type的action会一并触发,并不会覆盖
  entry.push(function wrappedActionHandler (payload) {
    let res = handler.call(store, {
      dispatch: local.dispatch,
      commit: local.commit,
      getters: local.getters,
      state: local.state,
      rootGetters: store.getters,
      rootState: store.state
    }, payload)
    // 会将action的结果变成Promise
    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
    }
  })
}

// 注册getter,保存到_wrappedGetters中
function registerGetter (store, type, rawGetter, local) {
  if (store._wrappedGetters[type]) {
    if (__DEV__) {
      console.error(`[vuex] duplicate getter key: ${type}`)
    }
    return
  }
  // 这里的store会在resetStoreState中传入
  store._wrappedGetters[type] = function wrappedGetter (store) {
    return rawGetter(
      local.state, // local state
      local.getters, // local getters
      store.state, // root state
      store.getters // root getters
    )
  }
}

调用

  • 全局调用

store.commit

export class Store {
  //...
  
  commit (_type, _payload, _options) {
    // check object-style commit
    // 统一格式化,兼容commit的多种形式
    const {
      type,
      payload,
      options
    } = unifyObjectStyle(_type, _payload, _options)

    const mutation = { type, payload }
    const entry = this._mutations[type]
    if (!entry) {
      if (__DEV__) {
        console.error(`[vuex] unknown mutation type: ${type}`)
      }
      return
    }
    // 非直接改变state的调用方式
    this._withCommit(() => {
      entry.forEach(function commitIterator (handler) {
        handler(payload)
      })
    })

    this._subscribers
      .slice() // shallow copy to prevent iterator invalidation if subscriber synchronously calls unsubscribe
      .forEach(sub => sub(mutation, this.state))

    if (
      __DEV__ &&
      options && options.silent
    ) {
      console.warn(
        `[vuex] mutation type: ${type}. Silent option has been removed. ` +
        'Use the filter functionality in the vue-devtools'
      )
    }
  }
  //...
}

store.dispatch

  dispatch (_type, _payload) {
    // check object-style dispatch
    // 统一格式化,兼容dispath的多种格式
    const {
      type,
      payload
    } = unifyObjectStyle(_type, _payload)

    const action = { type, payload }
    const entry = this._actions[type]
    if (!entry) {
      if (__DEV__) {
        console.error(`[vuex] unknown action type: ${type}`)
      }
      return
    }
    // 发布订阅相关
    try {
      this._actionSubscribers
        .slice() // shallow copy to prevent iterator invalidation if subscriber synchronously calls unsubscribe
        .filter(sub => sub.before)
        .forEach(sub => sub.before(action, this.state))
    } catch (e) {
      if (__DEV__) {
        console.warn(`[vuex] error in before action subscribers: `)
        console.error(e)
      }
    }
    
    // 在registerAction中已经将结果Promise化了
    const result = entry.length > 1
      ? Promise.all(entry.map(handler => handler(payload)))
      : entry[0](payload)

    return new Promise((resolve, reject) => {
      result.then(res => {
        // 发布订阅相关
        try {
          this._actionSubscribers
            .filter(sub => sub.after)
            .forEach(sub => sub.after(action, this.state))
        } catch (e) {
          if (__DEV__) {
            console.warn(`[vuex] error in after action subscribers: `)
            console.error(e)
          }
        }
        resolve(res)
      }, error => {
        try {
          this._actionSubscribers
            .filter(sub => sub.error)
            .forEach(sub => sub.error(action, this.state, error))
        } catch (e) {
          if (__DEV__) {
            console.warn(`[vuex] error in error action subscribers: `)
            console.error(e)
          }
        }
        reject(error)
      })
    })
  }

store.getters

  • resetStoreState 进行劫持
export function resetStoreState (store, state, hot, reserve = true) {
  const oldState = store._state
  const oldScope = store._scope

  // bind store public getters
  store.getters = {}
  // reset local getters cache
  store._makeLocalGettersCache = Object.create(null)
  
  // _wrappedGetters获取所有保存的getters
  const wrappedGetters = store._wrappedGetters
  // 保存每个getters方法
  const computedObj = {}
  // 保存每个getters方法的计算结果
  const computedCache = {}

  // create a new effect scope and create computed object inside it to avoid
  // getters (computed) getting destroyed on component unmount.
  const scope = effectScope(true)

  scope.run(() => {
    forEachValue(wrappedGetters, (fn, key) => {
      // use computed to leverage its lazy-caching mechanism
      // direct inline function use will lead to closure preserving oldState.
      // using partial to return function with only arguments preserved in closure environment.
      // 通过闭包每次获取最新的store
      computedObj[key] = partial(fn, store)
      // 通过computed缓存getters计算结果
      computedCache[key] = computed(() => computedObj[key]())
      // store.getters是给外部通过store直接调用getters的
      Object.defineProperty(store.getters, key, {
        get: () => computedCache[key].value,
        enumerable: true // for local getters
      })
    })
  })
  // ...
}

export function partial (fn, arg) {
  return function () {
    return fn(arg)
  }
}

store.state

  • 通过reactive将state变成响应式
export function resetStoreState (store, state, hot, reserve = true) {
  const oldState = store._state
  const oldScope = store._scope
   
  // ...	
	
  // create a new effect scope and create computed object inside it to avoid
  // getters (computed) getting destroyed on component unmount.
  const scope = effectScope(true)

  scope.run(() => {
    forEachValue(wrappedGetters, (fn, key) => {
   		// ...
    })
  })
  //将state保存到_state属性上
  store._state = reactive({
    data: state
  })

  // register the newly created effect scope to the store so that we can
  // dispose the effects when this method runs again in the future.
  store._scope = scope
  
  // ...

  if (oldState) {
    if (hot) {
      // dispatch changes in all subscribed watchers
      // to force getter re-evaluation for hot reloading.
      store._withCommit(() => {
        oldState.data = null
      })
    }
  }

 // ...
}

// 开发者调用时
export class Store {
  // ...
  get state () {
    return this._state.data
  }
  
  // ...
}
  • 局部调用

每个单独module的调用会在installModule里通过makeLocalContext进行劫持

  • 主要作用是为每个module调用内容的key拼接上命名空间
export function installModule (store, rootState, path, module, hot) {
  const isRoot = !path.length
  // 获取命名空间,格式为:x/xx/,选项没有设置namespaced则返回''
  // 所以格式可能有四种情况: x/xx/、 x/xx/'' 、 x/xx/''/xxx/ 、''
  // 注意上面的''只是进行说明,实际拼接''不会改变字符串,则上面实际上为:x/xx/、 x/xx/ 、 x/xx/xxx/ 、空字符串
  const namespace = store._modules.getNamespace(path)

  // ...
	
  // 为module创建一个局部上下文,用于当前module调用action等时,不用手动添加上级module的路径前缀
  const local = module.context = makeLocalContext(store, namespace, path)
  
  // ...
  module.forEachMutation((mutation, key) => {
    // key 是mutation定义时的命名
    const namespacedType = namespace + key
    registerMutation(store, namespacedType, mutation, local)
  })
  
  // ...
}

// makeLocalContext 
  • actions、mutations、state
    • 进行命名空间的拼接,最终还是调用挂在store上的全局commit、dispatch
function makeLocalContext (store, namespace, path) {
  const noNamespace = namespace === ''
  // 区分有命名空间和无命名空间的提交方式,有:type = namespace + type,无:直接使用type(会和操作根module一样)
  // 因为installModule中获取namespace,如果module没有声明namespaced,则不会添加该module的key到namespace
  // 通过registerMutation等进行注册时也不会有module的key
  // 则module内部调用则需要父namespace+type进行调用,如果有namespaced,module内调用只需要type即可
  // 如果嵌套的module都没有声明namespace,则相当于操作根module
  
  const local = {
    dispatch: noNamespace ? store.dispatch : (_type, _payload, _options) => { // 会自动在type前+namespace,module内调用只需要type即可
      // 统一提交格式(兼容第一个参数为对象的写法)
      const args = unifyObjectStyle(_type, _payload, _options)
      const { payload, options } = args
      let { type } = args

      if (!options || !options.root) {
        type = namespace + type
        if (__DEV__ && !store._actions[type]) {
          console.error(`[vuex] unknown local action type: ${args.type}, global type: ${type}`)
          return
        }
      }
      // 帮助拼接前缀路径
      return store.dispatch(type, payload)
    },
    // 和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 (__DEV__ && !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 state update
  // store.getters 和 store.state 都是在 resetStoreState 中定义
  Object.defineProperties(local, {
    getters: {
      get: noNamespace
        ? () => store.getters
        // 内部通过Object.defineProperty指向store.getters
        : () => makeLocalGetters(store, namespace)
    },
    // 根据当前module的path,从store.state上递进取对应的key,返回对应的state
    state: {
      get: () => getNestedState(store.state, path)
    }
  })

  return local
}
  • getters
  • 对于getters的调用则比较复杂
    • 如果module没有命名空间,则直接调用store.getter上的对应方法
      • 注意getters在installModule中通过registerGetter注册到了store._wrappedGetters上,然后又在resetStoreState中对store.getters进行了劫持处理,访问store.getter其实就是访问store._wrappedGetters
    • 对于局部getters,则会通过makeLocalGetters再次进行拦截,主要作用是帮助拼接命名空间路径,然后拦截指向store.getters

makeLocalGetters

export function makeLocalGetters (store, namespace) {
  if (!store._makeLocalGettersCache[namespace]) {
    const gettersProxy = {}
    // namespace是当前模块的命名空间,比如b模块为:'a/b/'
    const splitPos = namespace.length
    // 所有getters都会放在store.getters上,通过resetStoreState
    Object.keys(store.getters).forEach(type => {
      // skip if the target getter is not match this namespace
      //  只匹配对应namespace下的getters
      if (type.slice(0, splitPos) !== namespace) return

      // extract local getter type
      // 获取getter的方法名称
      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实际调用指向store.getters[type]
      // 这里的type是getters方法的完整路径,比如'a/b/fn',所以可以通过store.getters获取到
      Object.defineProperty(gettersProxy, localType, {
        get: () => store.getters[type],
        enumerable: true
      })
    })
    store._makeLocalGettersCache[namespace] = gettersProxy
  }

  return store._makeLocalGettersCache[namespace]
}
  • 总结一下,其实到这里可以知道(vuex就是一个究极大的对象):
    • vuex的所有state会挂载到根module的state上,具有上下级的层级结构(一个大的对象
    • vuex的所有getters、mutations、actions也会都放到一个大的对象上,没有层级结构

实现响应式

  • resetStoreState

在注册流程结束后,调用resetStoreState将状态变成响应式

  • state通过reactive
  • getters通过computed
export class Store {
	constructor (options = {}) {
    if (__DEV__) {
      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,
      devtools
    } = options

    // store internal state
    this._committing = false
    this._actions = Object.create(null)
    this._actionSubscribers = []
    this._mutations = Object.create(null)
    this._wrappedGetters = Object.create(null)
    // 给传递的选项中的每一个module创建module类进行管理,如果选项存在modules属性,会递归创建并挂在父module的_children属性下
    this._modules = new ModuleCollection(options)
    // 每个module的命名路径与module的映射表:{ 'a/b/moduleA/' : moduleA }
    this._modulesNamespaceMap = Object.create(null)
    this._subscribers = []
    // 报错每个module
    this._makeLocalGettersCache = Object.create(null)

    // EffectScope instance. when registering new getters, we wrap them inside
    // EffectScope so that getters (computed) would not be destroyed on
    // component unmount.
    // 使用 vue3 提供的 EffectScope API 这里让组件卸载时保留getters,手动维护清除依赖的行为
    this._scope = null

    this._devtools = devtools

    // bind commit and dispatch to self
    // 绑定 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)
    }

    // strict mode
    this.strict = strict
    // 获取 根module 的state
    const state = this._modules.root.state

    // init root module.
    // this also recursively registers all sub-modules
    // and collects all module getters inside this._wrappedGetters
    // 初始化所有module,收集所有module的getters放入this._wrappedGetters
    installModule(this, state, [], this._modules.root)

    // initialize the store state, which is responsible for the reactivity
    // (also registers _wrappedGetters as computed properties)
    // 进行响应式处理
    resetStoreState(this, state)

    // apply plugins
    plugins.forEach(plugin => plugin(this))
  }
}

resetStoreState

  • 对state、getters进行响应式处理
  • 通过 effectScope 避免计算属性随着组件卸载而失去响应性
    • 因为组件中的setup实际也是运行在一个effectScope中,当组建卸载时会自动卸载掉所有依赖
    • 通过effectScope(true)传递true参数,避免父effectScope影响子effectScope
export function resetStoreState (store, state, hot, reserve = true) {
  const oldState = store._state
  const oldScope = store._scope

  // bind store public getters
  store.getters = {}
  // reset local getters cache
  store._makeLocalGettersCache = Object.create(null)
  // _wrappedGetters保存module的getters
  const wrappedGetters = store._wrappedGetters
  // 保存每个getters方法
  const computedObj = {}
  // 保存每个getters方法的计算结果
  const computedCache = {}

  // create a new effect scope and create computed object inside it to avoid
  // getters (computed) getting destroyed on component unmount.
  // 通过effectScope避免getters随着组件卸载而卸载
  const scope = effectScope(true)

  scope.run(() => {
    forEachValue(wrappedGetters, (fn, key) => {
      // use computed to leverage its lazy-caching mechanism
      // direct inline function use will lead to closure preserving oldState.
      // using partial to return function with only arguments preserved in closure environment.
      // 通过闭包每次获取最新的store
      computedObj[key] = partial(fn, store)
      computedCache[key] = computed(() => computedObj[key]())
      // store.getters是给外部通过store直接调用getters的
      Object.defineProperty(store.getters, key, {
        get: () => computedCache[key].value,
        enumerable: true // for local getters
      })
    })
  })

  store._state = reactive({
    data: state
  })

  // register the newly created effect scope to the store so that we can
  // dispose the effects when this method runs again in the future.
  store._scope = scope

  // enable strict mode for new state
  if (store.strict) {
    enableStrictMode(store)
  }

  if (oldState) {
    if (hot) {
      // dispatch changes in all subscribed watchers
      // to force getter re-evaluation for hot reloading.
      store._withCommit(() => {
        oldState.data = null
      })
    }
  }

  // dispose previously registered effect scope if there is one.
  if (oldScope) {
    oldScope.stop()
  }
}

注册进业务组件

  • install

在业务组件中通过this就能访问到store

  install (app, injectKey) {
    app.provide(injectKey || storeKey, this)
    // 通过vue3的api,将store绑定到所有实例上
    app.config.globalProperties.$store = this

    const useDevtools = this._devtools !== undefined
      ? this._devtools
      : __DEV__ || __VUE_PROD_DEVTOOLS__

    if (useDevtools) {
      addDevtools(app, this)
    }
  }

热更新

  • hotUpdate
  • 当热更新时,需要将新的配置内容替换旧的,通过ModuleCollection的update进行更新
 hotUpdate (newOptions) {
   // 热更新module配置
   this._modules.update(newOptions)
   // 刷新store所有状态,并重新初始化响应式
   resetStore(this, true)
 }
  • 会递归调用update方法,将所有module的配置项进行更新
ModuleCollection {
 // 热更新相关,会根据最新module配置,重新初始化store
 update (rawRootModule) {
   // this.root为根module
   update([], this.root, rawRootModule)
 }
}

// 递归更新module配置,如果加入了新的module,需要手动刷新,主要是热更新中使用
function update (path, targetModule, newModule) {
  if (__DEV__) {
    assertRawModule(path, newModule)
  }

  // update target module
  targetModule.update(newModule)

  // update nested modules
  if (newModule.modules) {
    for (const key in newModule.modules) {
      if (!targetModule.getChild(key)) {
        if (__DEV__) {
          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]
      )
    }
  }
}
  • 调用module类的update方法进行更新
  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
    }
  }
  • 配置更新后,重新刷新状态并进行响应式

resetStore

export 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
  // 注册所有module
  installModule(store, state, [], store._modules.root, true)
  // 将state、getters进行响应式
  resetStoreState(store, state, hot, false)
}

4.1.0版本问题

  • 解决方案

  • 其实在4.1.0版本时,当通过vuex api进行添加module时,会使得计算属性丢失响应,原因是resetStoreState中会停止掉旧的scope所有依赖
  registerModule (path, rawModule, options = {}) {
    if (typeof path === 'string') path = [path]

    if (__DEV__) {
      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.')
    }

    this._modules.register(path, rawModule)
    installModule(this, this.state, path, this._modules.get(path), options.preserveState)
    // reset store to update getters...
    resetStoreState(this, this.state)
  }
  • 解决方式是将旧scope的依赖添加进新的scope中
export function resetStoreState (store, state, hot, reserve = true) {
  const oldState = store._state
  const oldScope = store._scope
  
  // ...
  if (oldState) {
    if (hot) {
      // dispatch changes in all subscribed watchers
      // to force getter re-evaluation for hot reloading.
      store._withCommit(() => {
        oldState.data = null
      })
    }
  }

  // dispose previously registered effect scope if there is one.
  if (oldScope) {
    if (reserve) {
      const deadEffects = []
      // 因为如果直接oldScope.stop(),原来数据的依赖性deps也会被清空,造成响应丢失,通过registerModule添加新module的时候会出问题
      oldScope.effects.forEach(effect => {
        if (effect.deps.length) {
        // Merge the effect that already have dependencies and prevent from being killed.
          scope.effects.push(effect)
        } else {
        // Collect the dead effects.
          deadEffects.push(effect)
        }
      })
      // Dispose the dead effects.
      oldScope.effects = deadEffects
    }
    oldScope.stop()
  }
}
  • 当然如果添加了上一步修改,那么还需要处理通过vuex api进行卸载module时,将旧的effects卸载掉
unregisterModule (path) {
  if (typeof path === 'string') path = [path]

  if (__DEV__) {
    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))
    delete parentState[path[path.length - 1]]
  })
  resetStore(this)
}
// 重置更新整个store
export 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 state
  // 最后一个参数传递false,表明不保存旧的effects
  resetStoreState(store, state, hot, false)
}

辅助函数

normalizeNamespace

  • 该工厂函数用来打平不同格式
//第一种格式
...mapActions([
  'some/nested/module/foo', // -> this['some/nested/module/foo']()
  'some/nested/module/bar' // -> this['some/nested/module/bar']()
])

//第二种格式
...mapActions('some/nested/module', [
  'foo', // -> this.foo()
  'bar' // -> this.bar()
])
  
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)
  }
}

normalizeMap

  • 处理通过MapXXX辅助函数调用时,传递的对象的不同格式
// 格式一
...mapState({
  a: state => state.a,
  b: state => state.b
})
// 格式二
...mapActions([
  'foo',
  'bar'
])
/**
 * Normalize the map
 * normalizeMap([1, 2, 3]) => [ { key: 1, val: 1 }, { key: 2, val: 2 }, { key: 3, val: 3 } ]
 * normalizeMap({a: 1, b: 2, c: 3}) => [ { key: 'a', val: 1 }, { key: 'b', val: 2 }, { key: 'c', val: 3 } ]
 * @param {Array|Object} map
 * @return {Object}
 */
function normalizeMap (map) {
  if (!isValidMap(map)) {
    return []
  }
  return Array.isArray(map)
    ? map.map(key => ({ key, val: key }))
    : Object.keys(map).map(key => ({ key, val: map[key] }))
}

getModuleByNamespace

  • 根据传递的命名空间路径,返回对应的module
function getModuleByNamespace (store, helper, namespace) {
  //
  const module = store._modulesNamespaceMap[namespace]
  if (__DEV__ && !module) {
    console.error(`[vuex] module namespace not found in ${helper}(): ${namespace}`)
  }
  return module
}
  • mapState

// 返回的是value为方法的对象,所以可以放入Vue组件中的computed中进行简化
export const mapState = normalizeNamespace((namespace, states) => {
  const res = {}
  if (__DEV__ && !isValidMap(states)) {
    console.error('[vuex] mapState: mapper parameter must be either an Array or an Object')
  }
  normalizeMap(states).forEach(({ key, val }) => {
    res[key] = function mappedState () {
      // 获取全局store的state、getters,这俩对象保存了所有module的state和getters
      let state = this.$store.state
      let getters = this.$store.getters
      if (namespace) {
        // 如果存在命名空间,则使用局部的state和getters,前面已经分析了局部的情况,直接调用方法的key即可,会自动拼接上命名空间前缀
        const module = getModuleByNamespace(this.$store, 'mapState', namespace)
        if (!module) {
          return
        }
        state = module.context.state
        getters = module.context.getters
      }
      return typeof val === 'function'
        ? val.call(this, state, getters)
        : state[val]
    }
    // mark vuex getter for devtools
    res[key].vuex = true
  })
  return res
})

  • mapGetters

    • 对于getters,直接从store.getters上拿,因为getters不需要传递参数
export const mapGetters = normalizeNamespace((namespace, getters) => {
  const res = {}
  if (__DEV__ && !isValidMap(getters)) {
    console.error('[vuex] mapGetters: mapper parameter must be either an Array or an Object')
  }
  normalizeMap(getters).forEach(({ key, val }) => {
    // The namespace has been mutated by normalizeNamespace
    val = namespace + val
    res[key] = function mappedGetter () {
      if (namespace && !getModuleByNamespace(this.$store, 'mapGetters', namespace)) {
        return
      }
      if (__DEV__ && !(val in this.$store.getters)) {
        console.error(`[vuex] unknown getter: ${val}`)
        return
      }
      return this.$store.getters[val]
    }
    // mark vuex getter for devtools
    res[key].vuex = true
  })
  return res
})

其他mapMutations、mapActions类似,就不做分析了

createNamespacedHelpers

  • 将所有mapHelper绑定一个统一的命名空间,简化调用
export const createNamespacedHelpers = (namespace) => ({
  mapState: mapState.bind(null, namespace),
  mapGetters: mapGetters.bind(null, namespace),
  mapMutations: mapMutations.bind(null, namespace),
  mapActions: mapActions.bind(null, namespace)
})

其他的一些utils

  • 监听直接修改state

在vuex中修改state都必须使用_withCommit,通过watch api来监测直接修改state

 _withCommit (fn) {
   const committing = this._committing
   this._committing = true
   fn()
   this._committing = committing
 }
// 判断是否是通过_withCommit修改,即不是外部直接修改state,通过_committing
function enableStrictMode (store) {
  watch(() => store._state.data, () => {
    if (__DEV__) {
      assert(store._committing, `do not mutate vuex store state outside mutation handlers.`)
    }
  }, { deep: true, flush: 'sync' })
}
  • 断言相关

// 一行代码实现断言
export function assert (condition, msg) {
  if (!condition) throw new Error(`[vuex] ${msg}`)
}

// 判断Promise
export function isPromise (val) {
  return val && typeof val.then === 'function'
}

// 判断对象
export function isObject (obj) {
  return obj !== null && typeof obj === 'object'
}

  • 遍历相关

/**
 * 会被给定对象的value、key传递给回调函数
 */
export function forEachValue (obj, fn) {
  Object.keys(obj).forEach(key => fn(obj[key], key))
}

和3.x版本的区别

  • 响应式区别

    • vuex 3.x的版本对应vue 2.x,所以在响应式方面并不能使用单独的reactive等api
export class Store {
	
    // 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()
    this._makeLocalGettersCache = Object.create(null)

    // 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进行响应式
    resetStoreVM(this, state)
}

resetStoreVM

  • 3.x版本响应式的核心是生成了一个单独的vue实例,vuex将数据挂载到该vue实例上
function resetStoreVM (store, state, hot) {
  const oldVm = store._vm

  // bind store public getters
  store.getters = {}
  // reset local getters cache
  store._makeLocalGettersCache = Object.create(null)
  const wrappedGetters = store._wrappedGetters
  const computed = {}
  forEachValue(wrappedGetters, (fn, key) => {
    // use computed to leverage its lazy-caching mechanism
    // direct inline function use will lead to closure preserving oldVm.
    // using partial to return function with only arguments preserved in closure environment.
    computed[key] = partial(fn, store)
    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
  Vue.config.silent = true
  // 核心是生成了一个vue实例
  store._vm = new Vue({
    data: {
      $$state: state
    },
    computed
  })
  Vue.config.silent = silent

  // enable strict mode for new vm
  if (store.strict) {
    enableStrictMode(store)
  }

  if (oldVm) {
    if (hot) {
      // dispatch changes in all subscribed watchers
      // to force getter re-evaluation for hot reloading.
      store._withCommit(() => {
        oldVm._data.$$state = null
      })
    }
    Vue.nextTick(() => oldVm.$destroy())
  }
}

// 获取state
export class Store {
  get state () {
    return this._vm._data.$$state
  }
}
  • 注册进组件区别

    • 在4.1.0版本中,vuex是通过app.config.globalProperties将store绑定到所有实例上
  install (app, injectKey) {
    app.provide(injectKey || storeKey, this)
    app.config.globalProperties.$store = this

    const useDevtools = this._devtools !== undefined
      ? this._devtools
      : __DEV__ || __VUE_PROD_DEVTOOLS__

    if (useDevtools) {
      addDevtools(app, this)
    }
  }
  • 在3.x版本中,是通过全局minxin的方式,将store绑定到所有组件实例上

    • 先将store绑定到根组件
new Vue({
  el: '#app',
  store: store,
})
  • 全局mixin
export default function (Vue) {
  const version = Number(Vue.version.split('.')[0])

  if (version >= 2) {
    Vue.mixin({ beforeCreate: vuexInit })
  }
  // ...
  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) { //子组件从父组件上取store
      this.$store = options.parent.$store
    }
  }
}

  JavaScript知识库 最新文章
ES6的相关知识点
react 函数式组件 & react其他一些总结
Vue基础超详细
前端JS也可以连点成线(Vue中运用 AntVG6)
Vue事件处理的基本使用
Vue后台项目的记录 (一)
前后端分离vue跨域,devServer配置proxy代理
TypeScript
初识vuex
vue项目安装包指令收集
上一篇文章      下一篇文章      查看所有文章
加:2022-12-25 10:59:18  更:2022-12-25 11:02:27 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年4日历 -2024/4/19 3:29:20-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码