乐趣区

关于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 源码解析(二)
退出移动版