2
本文基于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')

这个状态自管理应用包含以下几个部分:

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

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

image.png
但是,当我们的应用遇到多个组件共享状态时,单向数据流的简洁性很容易被破坏:

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

对于问题一,传参的方法对于多层嵌套的组件将会非常繁琐,并且对于兄弟组件间的状态传递无能为力。对于问题二,我们经常会采用父子组件直接引用或者通过事件来变更和同步状态的多份拷贝。以上的这些模式非常脆弱,通常会导致无法维护的代码。

因此,我们为什么不把组件的共享状态抽取出来,以一个全局单例模式管理呢?在这种模式下,我们的组件树构成了一个巨大的“视图”,不管在树的哪个位置,任何组件都能获取状态或者触发行为!

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

初始化示例

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
}

截屏2023-04-17 15.45.07.png

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对象,比如下面图片所示

截屏2023-01-26 02.27.37.png

那为什么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源码解析(二)

白边
209 声望37 粉丝

源码爱好者,已经完成vue2和vue3的源码解析+webpack5整体流程源码+vite4开发环境核心流程源码+koa2源码