• ⑦ Vuex源码解析


    1 安装

    1.1 Vuex 的安装

    Vue.use(Vuex);
    new Vue({
        el: '#app',
        store
    });
    

    1.2 Vuex 是怎样把 store 注入到 Vue 实例中的呢?

    • Vue 提供了 Vue.use 方法用来给 Vue.js 安装插件,内部通过调用插件的 install 方法进行插件的安装
    Vuex 的 install 实现
    • 防止 Vuex 被重复安装

    • 执行 applyMixin:执行 vuexInit 方法初始化 Vuex(将 vuexInint 混入 Vue 的 beforeCreate_init 方法)

    export default 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
      applyMixin(Vue)
    }
    
    vuexInit 使得所有组件都获取到同一份内存地址的 store 实例
    function vuexInit() {
        const options = this.$options
        if(options.store) {
            this.$store = typeof options.store === 'function'
            ? options.store()
            : options.store
        } else if(options.parent && options.parent.$store){
            this.$store = options.parent.$store
        }
     
    

    1.3 什么是 Store 实例?

    2 Store

    2.1 用 Vuex 提供的 Store 方法构造 Store 实例

    export default new Vuex.Store({
        strict: true,
        modules: {
            moduleA,
            moduleB
        }
    });
    

    2.2 Store 的构造函数

    constructor(options = {}) {
      if(!Vue && typeof window !== 'undefined' && window.Vue) {
        install(window.Vue)
      }
      if(process.env.NODE_ENV !== 'production') {
        assert(Vue, `must call Vue.use(Vuex) before creatin 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.`)
      }
    
      const {
        plugins = [],
        strict = false
      } = options
    
      let { state = {} } = options
      if(typeof state === 'function') {
        state = state()
      } 
    
      this._committing = false // 用来判断严格模式下是否是用mutation修改state的
      this._actions = Object.create(null) // 存放action
      this._mutations = Object.create(null) // 存放mutation
      this._wrappedGetters = Object.create(null) // 存放getter
      this._modules = new ModuleCollection(options) // module收集器
      this._modulesNamespaceMap = Object.create(null) // 根据namespace存放module
      this._subscribers = [] // 存放订阅者
      this._watcherVM = new Vue() // 用来实现Watch的Vue实例
      // 将dispatch与commit调用的this绑定为store对象本身,而不是实例
      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.strict = strict
    
      // 初始化根module,this._modules.root代表根module才独有保存Module对象
      installModule(this, state, [], this._modules.root)
    
      // 通过vm重设store,新建Vue对象使用Vue内部的响应式实现注册state以及computed
      resetStoreVM(this, state)
    
      // 调用插件
      plugins.forEach(plugin => plugin(this))
    
      // devtool插件
      if(Vue.config.devtools) {
        devtoolPlugin(this)
      } 
    }
    

    2.3 installModule 初始化 module

    • module 加上 namespace 名字空间后,注册 mutationaction 以及 getter,同时递归安装所有子 module
    function installModule(store, rootSatate, path, module, hot) {
      const isRoot = !path.length;
      const nameSpace = store._modules.getNamespace(path)
    
      // 如果有nameSpace则在_modulesNamespaceMap中注册
      if(module.namespaced) {
        store._modulesNamespaceMap[namespace] = module
      }
    
      if(!isRoot && !hot) {
        // 获取父级的state
        const parentState = getNestedState(rootState, path.slice(0, -1))
        const moduleName = path[path.length - 1]
        store.`_withCommit`(() => {
          // 将子module设成响应式的
          Vue.set(parentState, moduleName, module.state)
        })
      }
    
      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, namespaceType, getter, local)
      })
    
      // 递归安装module
      module.forEachChild((child, key) => {
        installModule(store, rootState, path.concat(key), child, hot)
      })
    }
    

    2.4 resetStoreVM

    • 通过 vm 重设 store,新建 Vue 对象使用 Vue 内部的响应式实现注册 state 以及 computed
    function resetStoreVM(store, state, hot) {
      const oldVm = store._vm
      store.getters = {}
      const wrappedGetters = store._wrappedGetters
      const computed = {}
    
      // 001 通过Object.defineProperty为每一个getter方法设置get方法
      forEachValue(wrappedGetters, (fn, key) => {
        computed[key] = () => fn(store)
        Object.defineProperty(store.getters, key, {
          get: () => store._vm[key],
          enumberable: true
        })
      })
    
      const silent = Vue.config.silent
      // 为了再new一个Vue实例的过程不会报出一切警告
      Vue.config.silent = true
      // 002 采用了new一个Vue对象来实现数据的“响应式化”
      store._vm = new Vue({
        data: {
          $$state: state
        },
        computed
      })
      ValueScope.config.silent = silent
    
      // 使能严格模式,保证修改store只能通过mutation
      if(store.strict) {
        enableStrictMode(store)
      }
    
      if(oldVm) {
        // 解除旧vm的state的引用,以及销毁旧的Vue对象
        if(hot) {
          store._withCommit(() => {
            oldVm._data.$$state = null
          })
        }
        Vue.nextTick(() => oldVm.$destroy())
      }
    }
    

    2.5 严格模式

    1. 通过 commit(mutation) 修改 state 数据时,会在调用 mutation 方法之前将 committing置为 true

    2. 接下来再通过 mutation 修改 state 中的数据,此时触发 $watch 中的回调断言 committing 是不会抛出异常的

    3. 直接修改 state 的数据时,触发 $watch 的回调执行断言,这时 committingfalse,则会抛出异常

    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 })
    }
    
    // Store的commit方法中,执行mutation的语句
    this._withCommit(() => {
      entry.forEach(function commitIterator(handler) {
        handler(payload)
      })
    })
    
    // _withCommit的实现
    _withCommit(fn) {
      const committing = this._committing
      // 调用withCommit修改state的值时会将store的committing值置为true
      this._committing = true
      fn()
      this._committing = committing
    }
    

    3 Store 提供的一些 API

    3.1 commit(mutation) -- 调用 mutation 的 commit 方法

    • commit 方法会根据 type 找到并调用 _mutations 中的所有 type 对应的 mutation 方法

    • 当没有 namespace 时,commit 方法会触发所有 module 中的 mutation 方法

    • 之后会执行 _subscribers 中的所有订阅者,提供给外部一个监视 state 变化的可能

    • state 通过 mutation 改变时,可以有效捕获这些变化

    commit(_type, _payload, _options) {
      const { type, payload, options } = unifyObjectStyle(_type, _payload, _options)
      const mutation = { type, payload }
      // 取出type对应的mutation的方法
      const entry = this._mutations[type]
      if(!entry) {
        if(process.env.NODE_ENV !== 'production') {
          console.error(`[]vuex] unknown mutation type: ${type}`)
        }
        return
      }
      // 执行mutation中的所有方法
      this._withCommit(() => {
        entry.forEach(function commitIterator(handler) {
          handler(payload)
        })
      })
      // 通知所有订阅者
      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'
        )
      }
    }
    
    // 注册一个订阅函数,返回取消订阅的函数
    subscribe(fn) {
      const subs = this._subscribers
      if(subs.indexOf(fn) < 0) {
        subs.push(fn)
      }
      return () => {
        const i = subs.indexOf(fn)
        if(i > -1) {
          subs.splice(i, 1)
        }
      }
    }
    

    3.2 dispatch(action) -- 调用 action 的 dispatch 方法

    dispatch(_type, _payload) {
      const { type, payload } = unifyObjectStyle(_type, _payload)
    
      // actions中取出type对应的action
      const entry = this._actions[type]
      if(!entry) {
        if(process.env.NODE_ENV !== 'production') {
          console.error(`[vuex] unknown action type: ${type}`)
        }
        return
      }
      // 是数组则包装Promise形成一个新的Promise,只有一个则返回第一个
      return entry.length > 1
      ? Promise.all(entry.map(handler => handler(payload)))
      : entry[0](payload)
    }
    
    遍历注册 action
    • 对 push 进_actionsaction 进行了一层封装
    1. 在进行 dispatch 的第一个参数中获取 statecommit 等方法

    2. 执行结果会被进行判断是否是 Promise,不会则会将其转为 Promise对象

    3. dispatch 时则从 _actions 中取出,返回

    function registerAction(store, type, handler, local) {
      // 取出type对应的action
      const entry = store._actions[type] || (store._actions[type] = [])
      // 对push进_actions的action进行封装
      entry.push(function wrappedActionHandler(payload, cb) {
        let res = handler.call(store, {
          dispatch: local.dispatch,
          commit: local.commit,
          getters: local.getters,
          state: local.state,
          rootGetters: store.getters,
          rootState: store.state
        }, payload, cb)
        if(!isPromise(res)) {
          // 不是Promise对象时,转为Promise对象
          res = Promise.resolve(res)
        }
        if(store._devtoolHook) {
          // 存在devtool插件时触发vuex的error给devtool
          return res.catch(err => {
            store._devtoolHook.emit('vuex:error', err)
            throw err
          })
        } else {
          return res
        }
      })
    }
    

    3.3 watch -- 观察一个 getter 方法

    watch(getter, cb, options) {
      if(process.env.NODE_ENV !== 'production') {
        assert(typeof getter === 'function', `store.watch only accepts a function.`)
      }
      // _watcherVM是Vue实例-> 采用了Vue内部的watch特性提供的一种观察数据getter变动的方法
      return this._watcherVM.$watch(() => getter(this.state, this.getter), cb, options)
    }
    

    3.4 registerModule -- 注册动态 module

    • 当业务进行异步加载时,可以通过该接口进行动态注册 module
    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.`)
      }
      // 注册
      this._modules.register(path, rawModule)
      // 初始化module
      installModule(this, this.state, path, this._modules.get(path))
      // 通过vm重设store,新建Vue对象使用Vue内部的响应式实现注册state以及computed
      resetStoreVM(this, this.state)
    }
    

    3.4 unregisterModule -- 注销动态 module

    • 先从 state 中删除模块,然后重制 store
    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(() => {
        // 获取父级的state
        const parentState = getNestedState(this.state, path.slice(0, -1))
        // 从父级中删除
        Vue.delete(parentState, path[path.length - 1])
      })
      // 重制store
      resetStore(this)
    }
    

    3.4 resetStore -- 重置 store

    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
      installModule(store, state, [], store._modules.root,true)
      resetStoreVM(store, state, hot)
    }
    

    4 插件 devtools

    // 从window对象的__VUE_DEVTOOLS_GLOBAL_HOOK__中获取devtool插件
    const devtoolHook = 
      typeof window !== 'undefined' &&
      window.__VUE_DEVTOOLS_GLOBAL_HOOK__
    
    export default function devtoolPlugin(store) {
      if(!devtoolHook) return
    
      // devtool插件实例存储在store的_devtoolHook上
      store._devtoolHook = devtoolHook
    
      // 触发vuex的初始化事件,并将store地址传给devtool插件,使插件获取store的实例
      devtoolHook.emit('vuex:init', store)
    
      // 监听travel-to-state事件
      devtoolHook.on('vuex:travel-to-state', targetState => {
        // 重制state
        store.replaceState(targetState)
      })
    
      // 订阅state的变化
      store.subscribe((mutation, state) => {
        devtoolHook.emit('vuex: mutation', mutation, state)
      })
    }
    
  • 相关阅读:
    js运算符逻辑!和instanceof的优先级
    一道关于数组的前端面试题
    关于变量提升
    关于offsetParent
    获取地址栏的参数列表,并转化为对象
    关于类型转换
    bootstrap-4
    bootstrap-3
    bootStrap-2
    bootStrap-1
  • 原文地址:https://www.cnblogs.com/pleaseAnswer/p/14331354.html
Copyright © 2020-2023  润新知