Vuex4.0.0 源码解析
创始人
2024-04-25 23:48:47
0

本文章基于以下版本撰写

  • VUE 版本: 3.0
  • VUEX 版本:4.0.0
  • Vuex仓库:https://github.com/vuejs/vuex/tree/v4.0.0
  • Vux文档:https://vuex.vuejs.org/zh/

文章目录

  • 在 vue 中使用 vuex
  • 从 createStore 引入讲起
  • ⭐️ ModuleCollection 模块处理
  • ⭐️⭐️ 初始化根 Moudle `installModule`
  • ⭐️⭐️⭐️对State进行响应式处理 `resetStoreState`
  • ⭐️⭐️⭐️⭐️ Store 全局注册

在 vue 中使用 vuex

import { createApp } from 'vue'
import { createStore } from 'vuex'// 创建一个新的 store 实例
const store = createStore({state () {return {count: 0}},mutations: {increment (state) {state.count++}}
})const app = createApp({ /* 根组件 */ })// 将 store 实例作为插件安装
app.use(store)

从 createStore 引入讲起

让我们先看一下 vuex/src 目录,引入 createStore 就是从 index.js 文件中引入
在这里插入图片描述

// src/index.js⭐️import { Store, createStore } from './store'
import { storeKey, useStore } from './injectKey'
import { mapState, mapMutations, mapGetters, mapActions, createNamespacedHelpers } from './helpers'
import { createLogger } from './plugins/logger'export default {version: '__VERSION__',Store,storeKey,⭐️createStore,useStore,mapState,mapMutations,mapGetters,mapActions,createNamespacedHelpers,createLogger
}export {Store,storeKey,createStore,useStore,mapState,mapMutations,mapGetters,mapActions,createNamespacedHelpers,createLogger
}

可以看到 createStore 是从 store 文件中引用,那么下面我们来看下 store 文件

// store.jsexport function createStore(options) {return new Store(options)
}

直接返回了一个 Store 的实例,所以我们知道 Store 肯定是一个 class,下面我们来看下 Store 类

// store.js
export class Store {constructor(options = {}) {if (__DEV__) {assert(typeof Promise !== 'undefined',`vuex requires a Promise polyfill in this browser.`)assert(this instanceof Store,`store must be called with the new operator.`)}const { plugins = [], strict = false, devtools } = options// 存储内部状态this._committing = falsethis._actions = Object.create(null)this._actionSubscribers = []this._mutations = Object.create(null)this._wrappedGetters = Object.create(null)⭐️this._modules = new ModuleCollection(options)this._modulesNamespaceMap = Object.create(null)this._subscribers = []this._makeLocalGettersCache = Object.create(null)this._scope = nullthis._devtools = devtoolsconst store = this// 在实例自身身上挂两个方法分别是原型上的dispatch 、commit方法,并将函数内部的this指针强行指向当前创建的store对象。const { dispatch, commit } = thisthis.dispatch = function boundDispatch(type, payload) {return dispatch.call(store, type, payload)}this.commit = function boundCommit(type, payload, options) {return commit.call(store, type, payload, options)}// strict modethis.strict = strictconst state = this._modules.root.state// 初始化根Moudule⭐️⭐️installModule(this, state, [], this._modules.root)// 对State进行响应式处理⭐️⭐️⭐️resetStoreState(this, state)// 应用插件plugins.forEach((plugin) => plugin(this))}⭐️⭐️⭐️⭐️install (app, injectKey) {app.provide(injectKey || storeKey, this)app.config.globalProperties.$store = this}
}

我们先看下 constructor 部分,我们知道通过 new 命令生成对象实例时,自动调用该方法。
1. 所以首先是存储内部状态
2. 在实例自身身上挂两个方法分别是原型上的dispatch 、commit方法,并将函数内部的this指针强行指向当前创建的store对象。
3. 初始化根Moudule
4. 对State进行响应式处理
5. 应用插件
6. 全局注册

⭐️ ModuleCollection 模块处理

我们从 this._modules = new ModuleCollection(options) 创建实例对象讲起,从字面意思可知是一个 module 收集的过程

// src/module/module-collection.jsexport default class ModuleCollection {constructor (rawRootModule) {// register root module (Vuex.Store options)this.register([], rawRootModule, false)}

可以看到是调用 register 方法

// src/module/module-collection.jsregister (path, rawModule, runtime = true) {if (__DEV__) {assertRawModule(path, rawModule)}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 modulesif (rawModule.modules) {forEachValue(rawModule.modules, (rawChildModule, key) => {this.register(path.concat(key), rawChildModule, runtime)})}}

创建 Module 实例,先挂在到 root 属性上,然后看有没有 modules 属性,有的话就递归,给每个模块都创建一个 Module 实例对象,

// src/module/module.jsexport default class Module {constructor (rawModule, runtime) {this.runtime = runtime// Store some children itemthis._children = Object.create(null)// Store the origin module object which passed by programmerthis._rawModule = rawModuleconst rawState = rawModule.state// Store the origin module's statethis.state = (typeof rawState === 'function' ? rawState() : rawState) || {}}}

这样每个模块就有自己单独的 state,实现独立管理自己模块的状态

⭐️⭐️ 初始化根 Moudle installModule

// src/store-util.jsexport function installModule (store, rootState, path, module, hot) {const isRoot = !path.lengthconst namespace = store._modules.getNamespace(path)// register in namespace map⭐️if (module.namespaced) {if (store._modulesNamespaceMap[namespace] && __DEV__) {console.error(`[vuex] duplicate namespace ${namespace} for the namespaced module ${path.join('/')}`)}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(() => {if (__DEV__) {if (moduleName in parentState) {console.warn(`[vuex] state field "${moduleName}" was overridden by a module with the same name at "${path.join('.')}"`)}}parentState[moduleName] = module.state})}⭐️⭐️⭐️const local = module.context = makeLocalContext(store, namespace, path)⭐️⭐️⭐️⭐️module.forEachMutation((mutation, key) => {const namespacedType = namespace + keyregisterMutation(store, namespacedType, mutation, local)})⭐️⭐️⭐️⭐️module.forEachAction((action, key) => {const type = action.root ? key : namespace + keyconst handler = action.handler || actionregisterAction(store, type, handler, local)})⭐️⭐️⭐️⭐️module.forEachGetter((getter, key) => {const namespacedType = namespace + keyregisterGetter(store, namespacedType, getter, local)})⭐️⭐️⭐️⭐️⭐️module.forEachChild((child, key) => {installModule(store, rootState, path.concat(key), child, hot)})
}

⭐️:
若module.namespaced = true : 此Module将被加入store._modulesNamespaceMap内,其key为Module嵌套的路径。

⭐️⭐️:
非root Module时:子Module.state注入到父节点的state对象里。

⭐️⭐️⭐️:
对store进行局部化,这里主要对module.namespaced= true 的module进行另外处理,其内部的成员都需要进行namespace路径处理处理。

⭐️⭐️⭐️⭐️:
对 mutation、action、getter 进行封装,放在 Store 对应的 _mutations、_actions、_wrappedGetters 里

⭐️⭐️⭐️⭐️⭐️:
若当前module含有子module时,遍历当前model的_children属性,迭代执行installModule。

⭐️⭐️⭐️对State进行响应式处理 resetStoreState

// src/store-util.jsexport function resetStoreState (store, state, hot) {const oldState = store._stateconst oldScope = store._scope// bind store public gettersstore.getters = {}// reset local getters cachestore._makeLocalGettersCache = Object.create(null)const wrappedGetters = store._wrappedGettersconst computedObj = {}const computedCache = {}// create a new effect scope and create computed object inside it to avoid// getters (computed) getting destroyed on component unmount.const scope = effectScope(true)⭐️scope.run(() => {forEachValue(wrappedGetters, (fn, key) => {// use computed to leverage its lazy-caching mechanism// direct inline function use will lead to closure preserving oldState.// using partial to return function with only arguments preserved in closure environment.computedObj[key] = partial(fn, store)computedCache[key] = computed(() => computedObj[key]())Object.defineProperty(store.getters, key, {get: () => computedCache[key].value,enumerable: true // for local getters})})})⭐️⭐️store._state = reactive({data: state})// register the newly created effect scope to the store so that we can// dispose the effects when this method runs again in the future.store._scope = scope// enable strict mode for new stateif (store.strict) {enableStrictMode(store)}if (oldState) {if (hot) {// dispatch changes in all subscribed watchers// to force getter re-evaluation for hot reloading.store._withCommit(() => {oldState.data = null})}}// dispose previously registered effect scope if there is one.if (oldScope) {oldScope.stop()}
}

⭐️:
将 getter 注册为计算属性

⭐️⭐️:
让 state 变为响应式对象

⭐️⭐️⭐️⭐️ Store 全局注册

我们知道当我们应用插件 app.use(store) 时候,会自动调用 install 方法

4.0版本 vuex 使用的是 provide / inject 来实现全局注册,因为 vue3 已经不支持 $ api

  install (app, injectKey) {app.provide(injectKey || storeKey, this)app.config.globalProperties.$store = this}

除了通过 app.config.globalProperties 设置全局属性$store,还provide了一个storeKey,这显然是为 useStore() 做准备。

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

相关内容

热门资讯

喜欢穿一身黑的男生性格(喜欢穿... 今天百科达人给各位分享喜欢穿一身黑的男生性格的知识,其中也会对喜欢穿一身黑衣服的男人人好相处吗进行解...
发春是什么意思(思春和发春是什... 本篇文章极速百科给大家谈谈发春是什么意思,以及思春和发春是什么意思对应的知识点,希望对各位有所帮助,...
网络用语zl是什么意思(zl是... 今天给各位分享网络用语zl是什么意思的知识,其中也会对zl是啥意思是什么网络用语进行解释,如果能碰巧...
为什么酷狗音乐自己唱的歌不能下... 本篇文章极速百科小编给大家谈谈为什么酷狗音乐自己唱的歌不能下载到本地?,以及为什么酷狗下载的歌曲不是...
家里可以做假山养金鱼吗(假山能... 今天百科达人给各位分享家里可以做假山养金鱼吗的知识,其中也会对假山能放鱼缸里吗进行解释,如果能碰巧解...
华为下载未安装的文件去哪找(华... 今天百科达人给各位分享华为下载未安装的文件去哪找的知识,其中也会对华为下载未安装的文件去哪找到进行解...
四分五裂是什么生肖什么动物(四... 本篇文章极速百科小编给大家谈谈四分五裂是什么生肖什么动物,以及四分五裂打一生肖是什么对应的知识点,希...
怎么往应用助手里添加应用(应用... 今天百科达人给各位分享怎么往应用助手里添加应用的知识,其中也会对应用助手怎么添加微信进行解释,如果能...
客厅放八骏马摆件可以吗(家里摆... 今天给各位分享客厅放八骏马摆件可以吗的知识,其中也会对家里摆八骏马摆件好吗进行解释,如果能碰巧解决你...
苏州离哪个飞机场近(苏州离哪个... 本篇文章极速百科小编给大家谈谈苏州离哪个飞机场近,以及苏州离哪个飞机场近点对应的知识点,希望对各位有...