关于vue.js:Vue3相关源码Vuex源码解析

本文基于Vuex 4.1.0版本源码进行剖析

文章内容

应用简略的源码展现Vuex的用法,并且基于用法中所波及到的源码进行剖析

介绍

上面的介绍摘录于Vuex官网文档,总结起来就是Vuex是一个具备响应式和肯定规定的全局数据管理对象

Vuex 是一个专为 Vue.js 利用程序开发的状态管理模式 + 库。它采纳集中式存储管理利用的所有组件的状态,并以相应的规定保障状态以一种可预测的形式发生变化

const Counter = {
  // 状态
  data () {
    return {
      count: 0
    }
  },
  // 视图
  template: `
    <div>{{ count }}</div>
  `,
  // 操作
  methods: {
    increment () {
      this.count++
    }
  }
}

createApp(Counter).mount('#app')

这个状态自治理利用蕴含以下几个局部:

  • 状态,驱动利用的数据源;
  • 视图,以申明形式将状态映射到视图;
  • 操作,响应在视图上的用户输出导致的状态变动。

以下是一个示意“单向数据流”理念的简略示意:

然而,当咱们的利用遇到多个组件共享状态时,单向数据流的简洁性很容易被毁坏:

  • 多个视图依赖于同一状态。
  • 来自不同视图的行为须要变更同一状态。

对于问题一,传参的办法对于多层嵌套的组件将会十分繁琐,并且对于兄弟组件间的状态传递无能为力。对于问题二,咱们常常会采纳父子组件间接援用或者通过事件来变更和同步状态的多份拷贝。以上的这些模式十分软弱,通常会导致无奈保护的代码。

因而,咱们为什么不把组件的共享状态抽取进去,以一个全局单例模式治理呢?在这种模式下,咱们的组件树形成了一个微小的“视图”,不论在树的哪个地位,任何组件都能获取状态或者触发行为!

通过定义和隔离状态治理中的各种概念并通过强制规定维持视图和状态间的独立性,咱们的代码将会变得更结构化且易保护。

初始化示例

import { createApp } from 'vue'
import { createStore } from 'vuex'
const moduleA = {
  state: () => ({ ... }),
  mutations: { ... },
  actions: { ... },
  getters: { ... }
}
// 创立一个新的 store 实例
const store = createStore({
  state () {
    return {
      count: 0
    }
  },
  mutations: {
    increment (state) {
      state.count++
    }
  },
  modules: {
    a: moduleA
  }
})
// store.state.a // -> moduleA 的状态

const app = createApp({ /* 根组件 */ })

// 将 store 实例作为插件装置
app.use(store)

createStore:创立store

从上面代码能够晓得,能够分为4个局部:

  • 第1局部:初始化dispatchcommit办法,对应Vuexactionsmutations
  • 第2局部:installModule()初始化root module,解决gettersmutationsactions,而后递归解决子module
  • 第3局部:resetStoreState()建设gettersstatecomputed关系,将state设置为响应式类型,解决oldStateoldScope
function createStore(options) {
    return new Store(options)
}
class Store {
    constructor(options = {}) {
        const { plugins = [] } = options
        // 第1局部
          this._modules = new ModuleCollection(options)
        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)
        }
        const state = this._modules.root.state
        // 第2局部
        installModule(this, state, [], this._modules.root)
        // 第3局部
        resetStoreState(this, state)
        // 第4局部
        plugins.forEach(plugin => plugin(this))
    }
}

第1局部:new ModuleCollection(options)

因为应用繁多状态树,利用的所有状态会集中到一个比拟大的对象。当利用变得非常复杂时,store 对象就有可能变得相当臃肿。
为了解决以上问题,Vuex 容许咱们将 store 宰割成模块(module)。每个模块领有本人的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样形式的宰割

从上面的代码能够晓得,new ModuleCollection(options)会进行rawRootModulenew Module()初始化,并且赋值给this.root,而后遍历rawRootModul.modules,对每一个module都进行new Module()的初始化和parent-child的关系建设

path是一个数组,每一层都是数组的一个元素,应用这个数组path能够找到对应的下级parent
runtime=true代表该module是否是运行时创立的module

class ModuleCollection {
    constructor(rawRootModule) {
        // rawRootModule代表createStore时传入的对象
        // 比方{state:{}, mutations:{}, modules: {}}
        this.register([], rawRootModule, false)
    }
    register(path, rawModule, runtime = true) {
        const newModule = new Module(rawModule, runtime)
        if (path.length === 0) {
            this.root = newModule
        } else {
            // 如果有嵌套多层,每一层都是数组的一个item
            // parent是目前rawModule的间接下级
            const parent = this.get(path.slice(0, -1))
            parent.addChild(path[path.length - 1], newModule)
        }

        if (rawModule.modules) {
            forEachValue(rawModule.modules, (rawChildModule, key) => {
                // key=注册的Module的名称
                this.register(path.concat(key), rawChildModule, runtime)
            })
        }
    }
}

new Module(rawModule, runtime)

如上面代码块所示,只是将目前的rawModule的属性拆解进去造成this.state以及创立多个辅助办法addChild()等等

class Module {
    constructor(rawModule, runtime) {
        this.runtime = runtime
        // Store some children item
        this._children = Object.create(null)
        // Store the origin module object which passed by programmer
        this._rawModule = rawModule
        const rawState = rawModule.state
        // Store the origin module's state
        this.state = (typeof rawState === 'function' ? rawState() : rawState) || {}
    }
    addChild(key, module) {
        this._children[key] = module
    }
}

第2局部:installModule

从上面代码块能够晓得,次要执行了

  • getNamespace():从root module开始,应用path.reduce()root->child->目前的module,检测是否配置namespaced=true,如果是则拼接它们对应的命名,也就是它们注册的store.modules[A]=moduleAA这个key
  • _modulesNamespaceMap:缓存该module,为mapActions等语法糖应用,上面会具体分析
  • parentState[moduleName]=module.state:通过getNestedState()root开始寻找到目前moduleparent state,通过path[path.length - 1]拿到目前module的名称,而后建设父子state之间的分割
  • makeLocalContext():创立一个上下文local,具备dispatchcommitgettersstate等属性
  • registerMutation():遍历module.mutations属性定义的所有办法,进行registerMutation()注册,具体分析请看上面
  • registerAction():遍历module.actions属性定义的所有办法,进行registerAction()注册,具体分析请看上面
  • registerAction():遍历module.getters属性定义的所有办法,进行registerGetter()注册,具体分析请看上面
  • child-installModule():递归调用installModule()办法,反复下面步骤进行子module的解决,具体分析请看上面

总结起来,就是先建设每一个state之间的关系,而后开始解决以后statemutationactiongetters,而后再解决以后statechildren(解决子modulemutationactiongetters

function installModule(store, rootState, path, module, hot) {
    const isRoot = !path.length
    const namespace = store._modules.getNamespace(path) // 拼接它后面parent所有命名空间
    // register in namespace map
    if (module.namespaced) {
        store._modulesNamespaceMap[namespace] = module
    }
    // set state
    if (!isRoot && !hot) {
        const parentState = getNestedState(rootState, path.slice(0, -1))
        const moduleName = path[path.length - 1]
        store._withCommit(() => {
            parentState[moduleName] = module.state
        })
    }
    const local = module.context = makeLocalContext(store, namespace, path)
    module.forEachMutation((mutation, key) => {
        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)
    })
    // 递归装置子模块
    module.forEachChild((child, key) => {
        installModule(store, rootState, path.concat(key), child, hot)
    })
}

makeLocalContext创立以后module的辅助办法,为主动传参做筹备

传递参数
store:最外层的store,包含state、getters、mutations、actions、modules的Object对象
namespace:拼接目前该module后面parent所有命名空间的字符串,比方root/child1/child2
path:每一层的数组汇合,每一个Item就是一层

local.dispatch

如果noNamespace为空,则间接应用store.dispatch,如果有命名空间,则应用一个新的办法,咱们要留神,这里传入的_type就是一个办法名称,而后咱们会应用type = namespace + type拼接命名空间

在上面的剖析中咱们会晓得,这个local变量其实是作为内部注册子Module的actions办法时能够传入的子Module的dispatch,换句话说,所有数据都寄存在根State中,咱们之所以在内部能够间接应用子Module的dispatch对象+办法名,外部是映射到根State[命名空间+办法名]而已

const moduleA = {
  // ...
  actions: {
    incrementIfOddOnRootSum ({ dispatch, commit, getters, rootGetters }) {
        // 这里的dispatch就是子Module的dispatch,能够间接dispatch("incrementIfOddOnRootSum")
        // rootState还要加上命名空间,比方rootState.dispatch("a/incrementIfOddOnRootSum")
    }
  }
}
local.commit

如果noNamespace为空,则间接应用store.commit,如果有命名空间,则应用一个新的办法,咱们要留神,这里传入的_type就是一个办法名称,而后咱们会应用type = namespace + type拼接命名空间

在上面的剖析中咱们会晓得,这个local变量其实是作为内部注册子Module的mutations办法时能够传入的子Module的dispatch,换句话说,所有数据都寄存在根State中,咱们之所以在内部能够间接应用子Module的dispatch对象+办法名,外部是映射到根State[命名空间+办法名]而已,具体例子相似下面的local.dispatch

function makeLocalContext(store, namespace, path) {
    const noNamespace = namespace === ''
    const local = {
        dispatch: noNamespace ? store.dispatch : (_type, _payload, _options) => {
            const args = unifyObjectStyle(_type, _payload, _options)
            const { payload, options } = args
            let { type } = args
            if (!options || !options.root) {
                type = namespace + type;
            }
            return store.dispatch(type, payload)
        },
        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;
            }
            store.commit(type, payload, options)
        }
    }
    // getters and state object must be gotten lazily
    // because they will be changed by state update
    Object.defineProperties(local, {
        getters: {
            get: noNamespace
                ? () => store.getters
                : () => makeLocalGetters(store, namespace)
        },
        state: {
            // getNestedState=path.reduce((state, key) => state[key], state)
            get: () => getNestedState(store.state, path)
        }
    })
    return local
}

local.getters拿到以后子Module的getters

如果noNamespace为空,则间接应用store.getters,如果有命名空间,则触发makeLocalGetters(),建设代理Object.defineProperty进行get()办法的映射,实质也是应用store.getters[type],比方store.getters["a/a_modules"]获取到子Modulegetters

function makeLocalGetters(store, namespace) {
    if (!store._makeLocalGettersCache[namespace]) {
        const gettersProxy = {};
        const splitPos = namespace.length;
        Object.keys(store.getters).forEach(type => {
            // type.slice(0, splitPos)="a/a_modules/"
            // namespace="a/a_modules/"
            if (type.slice(0, splitPos) !== namespace) return;
            // localType="moduleA_child_getters1"
            const localType = type.slice(splitPos);
            // gettersProxy["moduleA_child_getters1"]=store.getters["a/a_modules/moduleA_child_getters1"]
            Object.defineProperty(gettersProxy, localType, {
                get: () => store.getters[type],
                enumerable: true
            });
        });
        store._makeLocalGettersCache[namespace] = gettersProxy;
    }
    return store._makeLocalGettersCache[namespace];
}

那为什么要这样代理呢?

如上面具体实例所示,咱们晓得,一个子Module内部调用注册的getters对象中的办法是能够拿到currentGetters对象的

上面代码块中的moduleA_child_getters1()办法传入参数currentGetters就是以后子Module的getters对象,它同时也能够拿到currentGetters.moduleA_child_getter2办法,而外部中,是通过下面代码块中的Object.defineProperty代理拿到store.getters[命名空间+办法名]

换句话说,所有数据都寄存在根State中,咱们之所以在内部能够间接应用子Module的getters对象+办法名,外部是映射到根State[命名空间+办法名]而已

const moduleA_child = {
    namespaced: true,
    state() {
        return {
            id: "moduleA_child",
            count: 11,
            todos: [
                {id: 1, text: '...', done: true},
                {id: 2, text: '...', done: false}
            ]
        }
    },
    getters: {
        moduleA_child_getters1(state, currentGetters, rootState, rootGetters) {
            return state.todos.filter(todo => todo.done);
        },
        moduleA_child_getter2(){}
    }
}
local.state拿到以后子Module的state

如果有多层级的命名空间,比方root/child1/child2,我传递一个"child2"getNestedState()能够主动从root遍历到目前module拿到目前module所持有的state

如下面图片所示,path=["a", "a_modules"],所以getNestedState()=rootState["a"]["a_modules"]拿到子Modulestate

function getNestedState (state, path) {
    return path.reduce(function (state, key) { return state[key]; }, state)
}

registerGetter: 内部调用getters

function installModule(store, rootState, path, module, hot) {
    //...
    module.forEachGetter((getter, key) => {
        const namespacedType = namespace + key
        registerGetter(store, namespacedType, getter, local)
    })
    //...
}

registerGettertype是拼接它后面parent所有命名空间,实质上是应用store._wrappedGetters[命名空间]=目前module注册的getters办法

比方上面的具体实例,一共会执行两次registerGetter()
第一次: "a/"+"moduleA_getters1"
第二次: "a/a_modules/"+"moduleA_child_getters1"

const moduleA_child = {
    namespaced: true,
    getters: {
        moduleA_child_getters1(state, getters, rootState, rootGetters) {
            return state.todos.filter(todo => todo.done);
        }
    }
}
const moduleA = {
    namespaced: true,
    getters: {
        moduleA_getters1(state, getters, rootState, rootGetters) {
            return state.todos.filter(todo => todo.done);
        }
    },
    modules: {
        a_modules: moduleA_child
    }
}
const store = createStore({
    modules: {
        a: moduleA
    }
});

registerGetter()传入的参数rawGetter,就是下面示例中内部调用的moduleA_child_getters1()办法

由上面第3局部resetStoreState的剖析能够晓得,最终内部调用 store.getters.xxx 实质就是调用store._wrappedGetters.xxx,具体的逻辑放在上面第3局部resetStoreState进行剖析,这里只有把握为什么local可能拿到以后子modulestategetters即可

rawGetter传入参数为:

  • 一开始创立的上下文local.state拿到的目前子modulestate
  • 一开始创立的上下文local.getters拿到的目前子modulegetters
  • 传入原始根对象storestate
  • 传入原始根对象storegetters
function registerGetter (store, type, rawGetter, local) {
  if (store._wrappedGetters[type]) {
    return
  }
  // type="a/a_modules/moduleA_child_getters1"
  store._wrappedGetters[type] = function wrappedGetter (store) {
    return rawGetter(
      local.state, // local state
      local.getters, // local getters
      store.state, // root state
      store.getters // root getters
    )
  }
}

registerMutation: 内部调用commit

type是拼接它后面parent所有命名空间,实质上是应用store._mutations[type]=[],而后将目前module.mutations属性注册的办法存入该数组中,其中传入参数为

  • 传入原始根对象store
  • 一开始创立的上下文local.state拿到的目前子modulestate
  • 内部调用时传入的数据
function registerMutation (store, type, handler, local) {
    const entry = store._mutations[type] || (store._mutations[type] = [])
    entry.push(function wrappedMutationHandler (payload) {
        handler.call(store, local.state, payload)
    })
}

registerAction: 内部调用dispatch

type是拼接它后面parent所有命名空间,实质上是应用store._actions[type]=[],而后将目前module._actions属性注册的办法存入该数组中,其中传入参数为

  • 内部调用时传入的数据

而后将返回后果包裹成为一个Promise数据

function registerAction (store, type, handler, local) {
  const entry = store._actions[type] || (store._actions[type] = [])
  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)
    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
    }
  })
}

小结

创立的上下文local代表了以后子module,在创立过程中解决的gettersmutationsactions是为了内部调用时可能主动传入该参数,比方上面代码块中getters传入的参数(state, getters, rootState, rootGetters),在内部调用时不必特意传入这些参数,最终会主动传入,实质就是创立的上下文local的功绩

const foo = {
  namespaced: true,
  // ...
  getters: {
      someGetter (state, getters, rootState, rootGetters) {
        getters.someOtherGetter // -> 'foo/someOtherGetter'
        rootGetters.someOtherGetter // -> 'someOtherGetter'
        rootGetters['bar/someOtherGetter'] // -> 'bar/someOtherGetter'
      },
  }
}

第3局部:resetStoreState

步骤1: 内部调用state.getters映射到外部_wrappedGetters

当内部调用store.getters[xxx]时,从上面代码能够晓得,会触发computedCache[key].value
因为computedCachecomputedObj建设了computed计算关系,因而触发computedCache[key].value等于触发computedObj[key]()
触发computedObj[key](),实质就是触发fn(store),也就是_wrappedGetters[key]

function resetStoreState(store, state, hot) {
    // 步骤1:
    // 建设getters和wrappedGetters的computed关系
    // 实际上是建设getters和state的computed关系
    scope.run(() => {
        forEachValue(wrappedGetters, (fn, key) => {
            // key=命名空间+办法名
            computedObj[key] = partial(fn, store)
            computedCache[key] = computed(() => computedObj[key]())
            Object.defineProperty(store.getters, key, {
                get: () => computedCache[key].value,
                enumerable: true // for local getters
            })
        })
    })
}
function partial (fn, arg) {
  // arg = store
  return function () {
    return fn(arg)
  }
}

在下面第2局部的registerGetter(如上面的代码块所示),咱们wrappedGetters[key]理论就是某一个命名空间为key(比方root/child1/child2)的getters办法,传入参数为那个module.statemodule.stateroot.stateroot.getters

function registerGetter (store, type, rawGetter, local) {
  if (store._wrappedGetters[type]) {
    return
  }
  // type=命名空间+办法名,比方type="a/a_modules/moduleA_child_getters1"
  store._wrappedGetters[type] = function wrappedGetter (store) {
    return rawGetter(
      local.state, // local state
      local.getters, // local getters
      store.state, // root state
      store.getters // root getters
    )
  }
}

传入参数对应内部调用的代码如下代码块所示

  • state: 对应local.state
  • getters: 对应local.getters
  • rootState: 对应store.state
  • rootGetters: 对应store.getters
const foo = {
  namespaced: true,
  // ...
  getters: {
      someGetter (state, getters, rootState, rootGetters) {
        // 以后子module的getters,不必带命名空间
        getters.someOtherGetter // -> 'foo/someOtherGetter'
        rootGetters.someOtherGetter // -> 'someOtherGetter'
        // root的getters,须要带命名空间
        rootGetters['bar/someOtherGetter'] // -> 'bar/someOtherGetter'
      },
  }
}

步骤2: 将state转化为reactive

间接利用Vue3 reactivestate转化为响应式数据,并且Store.js中提供获取state的形式为获取this.state.data数据

function resetStoreState(store, state, hot) {
    // 步骤1:
    // 建设getters和wrappedGetters的computed关系
    // 实际上是建设getters和state的computed关系

    // 步骤2: 将state转化为reactive
    store._state = reactive({
        data: state
    })
}
class Store {
  get state () {
    return this._state.data
  }
}

步骤3: 解决oldState和oldScope

function resetStoreState(store, state, hot) {
    // 步骤1:
    // 建设getters和wrappedGetters的computed关系
    // 实际上是建设getters和state的computed关系
    // 步骤2: 将state转化为reactive
  
    // 步骤3: 解决oldState和oldScope
    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) {
        oldScope.stop()
    }

}

app.use(store):Vuex作为插件装置到Vue中

use(plugin, ...options) {
    if (plugin && isFunction(plugin.install)) {
        installedPlugins.add(plugin);
        plugin.install(app, ...options);
    }
    else if (isFunction(plugin)) {
        installedPlugins.add(plugin);
        plugin(app, ...options);
    }
    return app;
}

从下面Vue3的源码能够晓得,最终会触发Vuexinstall()办法

class Store {
  install (app, injectKey) {
    app.provide(injectKey || storeKey, this)
    app.config.globalProperties.$store = this
    //...省略_devtools相干代码
  }
}

从下面代码能够晓得,store是采纳provide的办法装置到Vue中,因而当初咱们能够晓得,为什么我能够应用useStore()Vue3 setup()中间接获取到store对象的(如上面代码块所示,应用inject获取store对象)

// src/injectKey.js
export const storeKey = 'store'
export function useStore (key = null) {
  return inject(key !== null ? key : storeKey)
}

而因为app.config.globalProperties.$store = this,因而咱们在Options API也能够通过this.$store获取到state对象,比方上面图片所示

那为什么app.config.globalProperties.$store = this的时候,咱们就能应用this.$store获取到state对象呢?

在咱们以前的文章《Vue3源码-整体渲染流程浅析》中,咱们在解说setupStatefulComponent()时有说到,instance.ctx进行了new Proxy()的拦挡

function setupStatefulComponent(instance, isSSR) {
    const Component = instance.type;
    //...省略一些简略的校验逻辑
    // 0. 创立渲染代理属性拜访缓存
    instance.accessCache = Object.create(null);
    // 1. 创立public实例 / 渲染代理,并且给它Object.defineProperty一个__v_skip属性
    // def(value, "__v_skip" /* SKIP */, true);
    instance.proxy = markRaw(new Proxy(instance.ctx, PublicInstanceProxyHandlers));
    const { setup } = Component;
    if (setup) {
    // 2. 解决setup()函数
      // ...进行一系列解决
    }
    else {
    // 3. 没有setup函数时,间接调用finishComponentSetup
        finishComponentSetup(instance, isSSR);
    }
}
const PublicInstanceProxyHandlers = {
    get({ _: instance }, key) {},
    set({ _: instance }, key, value) {},
    has({ _: { data, setupState, accessCache, ctx, appContext, propsOptions } }, key) {}
};

this.$store会触发get()办法的查找,查找过程十分繁冗,最终命中appContext.config.globalProperties,从全局数据中查找是否有key=$store,因而app.config.globalProperties.$store = this的时候,咱们就能应用this.$store获取到app.config.globalProperties.$store,从而获取到state对象

get({ _: instance }, key) {
  const { ctx, setupState, data, props, accessCache, type, appContext } = instance;
  //...省略很多其它查找过程
  const publicGetter = publicPropertiesMap[key];
  if (publicGetter) {
      // ...
  } else if ((cssModule = type.__cssModules) && (cssModule = cssModule[key])) {
      //...
  } else if (ctx !== EMPTY_OBJ && hasOwn(ctx, key)) {
     // ...
  } else if (((globalProperties = appContext.config.globalProperties),
      hasOwn(globalProperties, key)) {
      return globalProperties[key];
  }
}

动静注册和卸载Module

registerModule

从上面代码能够晓得,动静注册module时,会先实例化该模块,进行new Module(),而后递归实例化它的子modules
而后触发installModule()resetStoreState,实质跟createStore()创立store时的步骤统一,先获取命名空间组成的地址字符串,而后解决gettersmutationsactions跟命名空间地址的联合,最初进行state数据的响应式初始化和旧的状态的革除

// this._modules = new ModuleCollection(options)
registerModule(path, rawModule, options = {}) {
    if (typeof path === 'string') path = [path]

    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)
}
class ModuleCollection {
    register(path, rawModule, runtime = true) {
        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)
        }

        // register nested modules
        if (rawModule.modules) {
            forEachValue(rawModule.modules, (rawChildModule, key) => {
                this.register(path.concat(key), rawChildModule, runtime)
            })
        }
    }
}

unregisterModule

unregisterModule (path) {
  if (typeof path === 'string') path = [path]

  this._modules.unregister(path)
  this._withCommit(() => {
    const parentState = getNestedState(this.state, path.slice(0, -1))
    delete parentState[path[path.length - 1]]
  })
  resetStore(this)
}

从下面代码能够晓得,流程次要为:

  • 移除ModuleCollection的数据
  • 应用_withCommit移除该state数据
  • 最终重置整个store,从新初始化一遍

移除ModuleCollection的数据

unregister (path) {
  const parent = this.get(path.slice(0, -1))
  const key = path[path.length - 1]
  const child = parent.getChild(key)
  if (!child) {
    return
  }
  if (!child.runtime) {
    return
  }
  parent.removeChild(key)
}

应用_withCommit移除该state数据

_withCommit (fn) {
  const committing = this._committing
  this._committing = true
  fn()
  this._committing = committing
}

最终重置整个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
  // init all modules
  installModule(store, state, [], store._modules.root, true)
  // reset state
  resetStoreState(store, state, hot)
}

store.commit(): mutations同步更新数据

内部调用代码示例

const store = createStore({
  state: {
    count: 1
  },
  mutations: {
    increment (state) {
      // 变更状态
      state.count++
    }
  }
})
store.commit('increment')

源码剖析

依据typethis._mutations拿到对应的数组办法,这部分数组办法的初始化是在下面第2局部installModule()registerMutation初始化的
最初触发_subscribers订阅函数的执行

比方 Store.js提供了订阅办法,能够给一些插件应用,比方Vuex内置的Logger插件,会应用store.subscribe进行订阅,而后追踪state的变动

commit(_type, _payload, _options) {
    // check object-style commit
    const {
        type,
        payload,
        options
    } = unifyObjectStyle(_type, _payload, _options)

    const mutation = { type, payload }
    const entry = this._mutations[type]
    if (!entry) {
        return
    }
    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))
}
_withCommit (fn) {
  const committing = this._committing
  this._committing = true
  fn()
  this._committing = committing
}

store.commit(): actions异步更新数据

内部调用代码示例

store.dispatch('increment')

源码剖析

跟下面mutaions执行的代码逻辑大同小异,也是从this._actions中拿到对应的数组办法,这部分数组办法的初始化是在下面第2局部installModule()registerAction初始化的,不同点在于返回的后果是一个Promise,并且Vuex为咱们解决所有可能产生的谬误
同样也会触发_subscribers订阅函数的执行,这里有sub.beforesub.after

 dispatch (_type, _payload) {
    // check object-style dispatch
    const {
      type,
      payload
    } = unifyObjectStyle(_type, _payload)
    const action = { type, payload }
    const entry = this._actions[type]
    if (!entry) {
      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) {}
   
    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) {}
        resolve(res)
      }, error => {
        reject(error)
      })
    })
  }

语法糖mapXXX

内部调用代码示例

computed: {
  ...mapState('some/nested/module', {
    a: state => state.a,
    b: state => state.b
  }),
  ...mapGetters('some/nested/module', [
    'someGetter', // -> this.someGetter
    'someOtherGetter', // -> this.someOtherGetter
  ])
},
methods: {
  ...mapActions('some/nested/module', [
    'foo', // -> this.foo()
    'bar' // -> this.bar()
  ])
}

mapState

从上面代码块能够晓得,传入namespace能够利用getModuleByNamespace()办法疾速找到对应的module对象,而后返回对应module对应的state[val],最终拿到的res是一个Object对象

const mapState = normalizeNamespace((namespace, states) => {
  const res = {}
  normalizeMap(states).forEach(({ key, val }) => {
    res[key] = function mappedState () {
      let state = this.$store.state
      let getters = this.$store.getters
      if (namespace) {
        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
})
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] }))
}

mapState整体流程能够概括为:
利用getModuleByNamespace()从初始化的_modulesNamespaceMap拿到指定的Module.js对象,而module.context是在installModule()时建设的上下文,它进行了该module对应gettersmutationsactions等属性的解决(初始化时进行了命名空间的解决),可能间接通过该module.context.state间接通过key拿到对应的数据

//mapState调用
function getModuleByNamespace (store, helper, namespace) {
  const module = store._modulesNamespaceMap[namespace]
  return module
}

// createStore初始化时调用
function installModule() {
  const local = module.context = makeLocalContext(store, namespace, path)
}
export 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
  }
}

因而mapState返回的对象为

{
 a:  this.$store.state.some.nested.module.a,
 b:  this.$store.state.some.nested.module.b
}

mapMutationsmapGettersmapActions等逻辑跟mapState简直一样,这里不再反复剖析

Vue系列其它文章

  1. Vue2源码-响应式原理浅析
  2. Vue2源码-整体流程浅析
  3. Vue2源码-双端比拟diff算法 patchVNode流程浅析
  4. Vue3源码-响应式零碎-依赖收集和派发更新流程浅析
  5. Vue3源码-响应式零碎-Object、Array数据响应式总结
  6. Vue3源码-响应式零碎-Set、Map数据响应式总结
  7. Vue3源码-响应式零碎-ref、shallow、readonly相干浅析
  8. Vue3源码-整体流程浅析
  9. Vue3源码-diff算法-patchKeyChildren流程浅析
  10. Vue3相干源码-Vue Router源码解析(一)
  11. Vue3相干源码-Vue Router源码解析(二)

评论

发表回复

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

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理