vuex 源码整体架构学习

这是确保这两个函数里的 this 是 store 实例

// 严格模式,默认是false

this.strict = strict

// 根模块的state

const state = this._modules.root.state

// init root module.

// this also recursively registers all sub-modules

// and collects all module getters inside this._wrappedGetters

installModule(this, state, [], this._modules.root)

// initialize the store vm, which is responsible for the reactivity

// (also registers _wrappedGetters as computed properties)

resetStoreVM(this, state)

上述这段代码 installModule(this, state, [], this._modules.root)

初始化 根模块。

并且也递归的注册所有子模块。

并且收集所有模块的 getters 放在 this._wrappedGetters 里面。

resetStoreVM(this, state)

初始化 store._vm 响应式的

并且注册 _wrappedGetters 作为 computed 的属性

plugins.forEach(plugin => plugin(this))

插件:把实例对象 store 传给插件函数,执行所有插件。

const useDevtools = options.devtools !== undefined ? options.devtools : Vue.config.devtools

if (useDevtools) {

devtoolPlugin(this)

}

初始化 vue-devtool 开发工具。

参数 devtools 传递了取 devtools 否则取Vue.config.devtools 配置。

初读这个构造函数的全部源代码。会发现有三个地方需要重点看。分别是:

this._modules = new ModuleCollection(options)

installModule(this, state, [], this._modules.root)

resetStoreVM(this, state)

阅读时可以断点调试,赋值语句this._modules = new ModuleCollection(options),如果暂时不想看,可以直接看返回结果。installModuleresetStoreVM函数则可以断点调试。

class ModuleCollection

收集模块,构造模块树结构。

注册根模块 参数 rawRootModule 也就是 Vuex.Store 的 options 参数

未加工过的模块(用户自定义的),根模块

export default class ModuleCollection {

constructor (rawRootModule) {

// register root module (Vuex.Store options)

this.register([], rawRootModule, false)

}

}

/**

  • 注册模块

  • @param {Array} path 路径

  • @param {Object} rawModule 原始未加工的模块

  • @param {Boolean} runtime runtime 默认是 true

*/

register (path, rawModule, runtime = true) {

// 非生产环境 断言判断用户自定义的模块是否符合要求

if (process.env.NODE_ENV !== ‘production’) {

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 modules

// 递归注册子模块

if (rawModule.modules) {

forEachValue(rawModule.modules, (rawChildModule, key) => {

this.register(path.concat(key), rawChildModule, runtime)

})

}

}

class Module

// Base data struct for store’s module, package with some attribute and method

// store 的模块 基础数据结构,包括一些属性和方法

export default class Module {

constructor (rawModule, runtime) {

// 接收参数 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

// 模块 state

const rawState = rawModule.state

// Store the origin module’s state

// 原始Store 可能是函数,也可能是是对象,是假值,则赋值空对象。

this.state = (typeof rawState === ‘function’ ? rawState() : rawState) || {}

}

}

经过一系列的注册后,最后 this._modules = new ModuleCollection(options) this._modules 的值是这样的。笔者画了一张图表示:

installModule 函数

function installModule (store, rootState, path, module, hot) {

// 是根模块

const isRoot = !path.length

// 命名空间 字符串

const namespace = store._modules.getNamespace(path)

if (module.namespaced) {

// 省略代码:模块命名空间map对象中已经有了,开发环境报错提示重复

// module 赋值给 _modulesNamespaceMap[namespace]

store._modulesNamespaceMap[namespace] = module

}

// … 后续代码 移出来 待读解释

}

注册 state

// set state

// 不是根模块且不是热重载

if (!isRoot && !hot) {

// 获取父级的state

const parentState = getNestedState(rootState, path.slice(0, -1))

// 模块名称

// 比如 cart

const moduleName = path[path.length - 1]

// state 注册

store._withCommit(() => {

// 省略代码:非生产环境 报错 模块 state 重复设置

Vue.set(parentState, moduleName, module.state)

})

}

最后得到的是类似这样的结构且是响应式的数据 实例 Store.state 比如:

{

// 省略若干属性和方法

// 这里的 state 是只读属性 可搜索 get state 查看,上文写过

state: {

cart: {

checkoutStatus: null,

items: []

}

}

}

const local = module.context = makeLocalContext(store, namespace, path)

module.context 这个赋值主要是给 helpers 中 mapStatemapGettersmapMutationsmapActions四个辅助函数使用的。

生成本地的dispatch、commit、getters和state。

主要作用就是抹平差异化,不需要用户再传模块参数。

遍历注册 mutation

module.forEachMutation((mutation, key) => {

const namespacedType = namespace + key

registerMutation(store, namespacedType, mutation, local)

})

/**

  • 注册 mutation

  • @param {Object} store 对象

  • @param {String} type 类型

  • @param {Function} handler 用户自定义的函数

  • @param {Object} local local 对象

*/

function registerMutation (store, type, handler, local) {

// 收集的所有的mutations找对应的mutation函数,没有就赋值空数组

const entry = store._mutations[type] || (store._mutations[type] = [])

// 最后 mutation

entry.push(function wrappedMutationHandler (payload) {

/**

  • mutations: {

  • pushProductToCart (state, { id }) {

  •    console.log(state);
    
  • }

  • }

  • 也就是为什么用户定义的 mutation 第一个参数是state的原因,第二个参数是payload参数

*/

handler.call(store, local.state, payload)

})

}

遍历注册 action

module.forEachAction((action, key) => {

const type = action.root ? key : namespace + key

const handler = action.handler || action

registerAction(store, type, handler, local)

})

/**

  • 注册 mutation

  • @param {Object} store 对象

  • @param {String} type 类型

  • @param {Function} handler 用户自定义的函数

  • @param {Object} local local 对象

*/

function registerAction (store, type, handler, local) {

const entry = store._actions[type] || (store._actions[type] = [])

// payload 是actions函数的第二个参数

entry.push(function wrappedActionHandler (payload) {

/**

  • 也就是为什么用户定义的actions中的函数第一个参数有

  • { dispatch, commit, getters, state, rootGetters, rootState } 的原因

  • actions: {

  • checkout ({ commit, state }, products) {

  •    console.log(commit, state);
    
  • }

  • }

*/

let res = handler.call(store, {

dispatch: local.dispatch,

commit: local.commit,

getters: local.getters,

state: local.state,

rootGetters: store.getters,

rootState: store.state

}, payload)

/**

  • export function isPromise (val) {

return val && typeof val.then === ‘function’

}

  • 判断如果不是Promise Promise 化,也就是为啥 actions 中处理异步函数

也就是为什么构造函数中断言不支持promise报错的原因

vuex需要Promise polyfill

assert(typeof Promise !== ‘undefined’, vuex requires a Promise polyfill in this browser.)

*/

if (!isPromise(res)) {

res = Promise.resolve(res)

}

// devtool 工具触发 vuex:error

if (store._devtoolHook) {

// catch 捕获错误

return res.catch(err => {

store._devtoolHook.emit(‘vuex:error’, err)

// 抛出错误

throw err

})

} else {

// 然后函数执行结果

return res

}

})

}

遍历注册 getter

module.forEachGetter((getter, key) => {

const namespacedType = namespace + key

registerGetter(store, namespacedType, getter, local)

})

/**

  • 注册 getter

  • @param {Object} store Store实例

  • @param {String} type 类型

  • @param {Object} rawGetter 原始未加工的 getter 也就是用户定义的 getter 函数

  • @examples 比如 cartProducts: (state, getters, rootState, rootGetters) => {}

  • @param {Object} local 本地 local 对象

*/

function registerGetter (store, type, rawGetter, local) {

// 类型如果已经存在,报错:已经存在

if (store._wrappedGetters[type]) {

if (process.env.NODE_ENV !== ‘production’) {

console.error([vuex] duplicate getter key: ${type})

}

return

}

// 否则:赋值

store._wrappedGetters[type] = function wrappedGetter (store) {

/**

  • 这也就是为啥 getters 中能获取到 (state, getters, rootState, rootGetters) 这些值的原因

  • getters = {

  •  cartProducts: (state, getters, rootState, rootGetters) => {
    
  •    console.log(state, getters, rootState, rootGetters);
    
  •  }
    
  • }

*/

return rawGetter(

local.state, // local state

local.getters, // local getters

store.state, // root state

store.getters // root getters

)

}

}

遍历注册 子模块

module.forEachChild((child, key) => {

installModule(store, rootState, path.concat(key), child, hot)

})

resetStoreVM 函数

resetStoreVM(this, state, hot)

初始化 store._vm 响应式的

并且注册 _wrappedGetters 作为 computed 的属性

function resetStoreVM (store, state, hot) {

// 存储一份老的Vue实例对象 _vm

const oldVm = store._vm

// bind store public getters

// 绑定 store.getter

store.getters = {}

// reset local getters cache

// 重置 本地getters的缓存

store._makeLocalGettersCache = Object.create(null)

// 注册时收集的处理后的用户自定义的 wrappedGetters

const wrappedGetters = store._wrappedGetters

// 声明 计算属性 computed 对象

const computed = {}

// 遍历 wrappedGetters 赋值到 computed 上

forEachValue(wrappedGetters, (fn, key) => {

// use computed to leverage its lazy-caching mechanism

// direct inline function use will lead to closure preserving oldVm.

// using partial to return function with only arguments preserved in closure environment.

/**

  • partial 函数

  • 执行函数 返回一个新函数

export function partial (fn, arg) {

return function () {

return fn(arg)

}

}

*/

computed[key] = partial(fn, store)

// getter 赋值 keys

Object.defineProperty(store.getters, key, {

get: () => store._vm[key],

// 可以枚举

enumerable: true // for local getters

})

})

// use a Vue instance to store the state tree

// suppress warnings just in case the user has added

// some funky global mixins

// 使用一个 Vue 实例对象存储 state 树

// 阻止警告 用户添加的一些全局mixins

// 声明变量 silent 存储用户设置的静默模式配置

const silent = Vue.config.silent

// 静默模式开启

Vue.config.silent = true

store._vm = new Vue({

data: {

$$state: state

},

computed

})

// 把存储的静默模式配置赋值回来

Vue.config.silent = silent

// enable strict mode for new vm

// 开启严格模式 执行这句

// 用 $watch 观测 state,只能使用 mutation 修改 也就是 _withCommit 函数

if (store.strict) {

enableStrictMode(store)

}

// 如果存在老的 _vm 实例

if (oldVm) {

// 热加载为 true

if (hot) {

// dispatch changes in all subscribed watchers

// to force getter re-evaluation for hot reloading.

// 设置 oldVm._data.$$state = null

store._withCommit(() => {

oldVm._data.$$state = null

})

}

// 实例销毁

Vue.nextTick(() => oldVm.$destroy())

}

}

到此,构造函数源代码看完了,接下来看 Vuex.Store 的 一些 API 实现。

Vuex.Store 实例方法


Vuex API 文档

commit

提交 mutation

commit (_type, _payload, _options) {

// check object-style commit

// 统一成对象风格

const {

type,

payload,

options

} = unifyObjectStyle(_type, _payload, _options)

const mutation = { type, payload }

// 取出处理后的用户定义 mutation

const entry = this._mutations[type]

// 省略 非生产环境的警告代码 …

this._withCommit(() => {

// 遍历执行

entry.forEach(function commitIterator (handler) {

handler(payload)

})

})

// 订阅 mutation 执行

this._subscribers.forEach(sub => sub(mutation, this.state))

// 省略 非生产环境的警告代码 …

}

commit 支持多种方式。比如:

store.commit(‘increment’, {

count: 10

})

// 对象提交方式

store.commit({

type: ‘increment’,

count: 10

})

unifyObjectStyle函数将参数统一,返回 { type, payload, options }

dispatch

分发 action

dispatch (_type, _payload) {

// check object-style dispatch

// 获取到type和payload参数

const {

type,

payload

} = unifyObjectStyle(_type, _payload)

// 声明 action 变量 等于 type和payload参数

const action = { type, payload }

// 入口,也就是 _actions 集合

const entry = this._actions[type]

// 省略 非生产环境的警告代码 …

try {

this._actionSubscribers

.filter(sub => sub.before)

.forEach(sub => sub.before(action, this.state))

} catch (e) {

if (process.env.NODE_ENV !== ‘production’) {

console.warn([vuex] error in before action subscribers: )

console.error(e)

}

}

const result = entry.length > 1

? Promise.all(entry.map(handler => handler(payload)))
entry 0

return result.then(res => {

try {

this._actionSubscribers

.filter(sub => sub.after)

.forEach(sub => sub.after(action, this.state))

} catch (e) {

if (process.env.NODE_ENV !== ‘production’) {

console.warn([vuex] error in after action subscribers: )

console.error(e)

}

}

return res

})

}

replaceState

替换 store 的根状态,仅用状态合并或时光旅行调试。

replaceState (state) {

this._withCommit(() => {

this._vm._data.$$state = state

})

}

watch

响应式地侦听 fn 的返回值,当值改变时调用回调函数。

/**

  • 观测某个值

  • @param {Function} getter 函数

  • @param {Function} cb 回调

  • @param {Object} options 参数对象

*/

watch (getter, cb, options) {

if (process.env.NODE_ENV !== ‘production’) {

assert(typeof getter === ‘function’, store.watch only accepts a function.)

}

return this._watcherVM.$watch(() => getter(this.state, this.getters), cb, options)

}

subscribe

订阅 store 的 mutation

subscribe (fn) {

return genericSubscribe(fn, this._subscribers)

}

// 收集订阅者

function genericSubscribe (fn, subs) {

if (subs.indexOf(fn) < 0) {

subs.push(fn)

}

return () => {

const i = subs.indexOf(fn)

if (i > -1) {

subs.splice(i, 1)

}

}

}

subscribeAction

订阅 store 的 action

subscribeAction (fn) {

const subs = typeof fn === ‘function’ ? { before: fn } : fn

return genericSubscribe(subs, this._actionSubscribers)

}

registerModule

注册一个动态模块。

/**

  • 动态注册模块

  • @param {Array|String} path 路径

  • @param {Object} rawModule 原始未加工的模块

  • @param {Object} options 参数选项

*/

registerModule (path, rawModule, options = {}) {

// 如果 path 是字符串,转成数组

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…

// 设置 resetStoreVM

resetStoreVM(this, this.state)

}

unregisterModule

卸载一个动态模块。

/**

  • 注销模块

  • @param {Array|String} path 路径

*/

unregisterModule (path) {

// 如果 path 是字符串,转成数组

if (typeof path === ‘string’) path = [path]

// 省略 非生产环境 报错代码 …

// 手动调用模块注销

this._modules.unregister(path)

this._withCommit(() => {

// 注销这个模块

const parentState = getNestedState(this.state, path.slice(0, -1))

Vue.delete(parentState, path[path.length - 1])

})

// 重置 Store

resetStore(this)

}

hotUpdate

热替换新的 action 和 mutation

// 热加载

hotUpdate (newOptions) {

// 调用的是 ModuleCollection 的 update 方法,最终调用对应的是每个 Module 的 update

this._modules.update(newOptions)

// 重置 Store

resetStore(this, true)

}

组件绑定的辅助函数


文件路径:vuex/src/helpers.js

mapState

为组件创建计算属性以返回 Vuex store 中的状态。

export const mapState = normalizeNamespace((namespace, states) => {

const res = {}

// 非生产环境 判断参数 states 必须是数组或者是对象

if (process.env.NODE_ENV !== ‘production’ && !isValidMap(states)) {

console.error(‘[vuex] mapState: mapper parameter must be either an Array or an Object’)

}

normalizeMap(states).forEach(({ key, val }) => {

res[key] = function mappedState () {

let state = this.$store.state

let getters = this.$store.getters

// 传了参数 namespace

if (namespace) {

// 用 namespace 从 store 中找一个模块。

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]

}

// 标记为 vuex 方便在 devtools 显示

// mark vuex getter for devtools

res[key].vuex = true

})

return res

})

normalizeNamespace 标准化统一命名空间

function normalizeNamespace (fn) {

return (namespace, map) => {

// 命名空间没传,交换参数,namespace 为空字符串

if (typeof namespace !== ‘string’) {

map = namespace

namespace = ‘’

} else if (namespace.charAt(namespace.length - 1) !== ‘/’) {

// 如果是字符串,最后一个字符不是 / 添加 /

// 因为 _modulesNamespaceMap 存储的是这样的结构。

/**

  • _modulesNamespaceMap:

cart/: {}

products/: {}

}

  • */

namespace += ‘/’

}

return fn(namespace, map)

}

}

// 校验是否是map 是数组或者是对象。

function isValidMap (map) {

return Array.isArray(map) || isObject(map)

}

/**

  • Normalize the map

  • 标准化统一 map,最终返回的是数组

  • normalizeMap([1, 2, 3]) => [ { key: 1, val: 1 }, { key: 2, val: 2 }, { key: 3, val: 3 } ]

  • normalizeMap({a: 1, b: 2, c: 3}) => [ { key: ‘a’, val: 1 }, { key: ‘b’, val: 2 }, { key: ‘c’, val: 3 } ]

  • @param {Array|Object} map

  • @return {Object}

*/

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] }))

}

module.context 这个赋值主要是给 helpers 中 mapStatemapGettersmapMutationsmapActions四个辅助函数使用的。

// 在构造函数中 installModule 中

const local = module.context = makeLocalContext(store, namespace, path)

这里就是抹平差异,不用用户传递命名空间,获取到对应的 commit、dispatch、state、和 getters

getModuleByNamespace

function getModuleByNamespace (store, helper, namespace) {

// _modulesNamespaceMap 这个变量在 class Store installModule 函数中赋值的

const module = store._modulesNamespaceMap[namespace]

if (process.env.NODE_ENV !== ‘production’ && !module) {

console.error([vuex] module namespace not found in ${helper}(): ${namespace})

}

return module

}

看完这些,最后举个例子: vuex/examples/shopping-cart/components/ShoppingCart.vue

computed: {

…mapState({

checkoutStatus: state => state.cart.checkoutStatus

}),

}

没有命名空间的情况下,最终会转换成这样

computed: {

checkoutStatus: this.$store.state.checkoutStatus

}

假设有命名空间’ruochuan’,

computed: {

…mapState(‘ruochuan’, {

checkoutStatus: state => state.cart.checkoutStatus

}),

}

则会转换成:

computed: {

checkoutStatus: this.$store._modulesNamespaceMap.[‘ruochuan/’].context.checkoutStatus

}

mapGetters

为组件创建计算属性以返回 getter 的返回值。

export const mapGetters = normalizeNamespace((namespace, getters) => {

const res = {}

// 省略代码:非生产环境 判断参数 getters 必须是数组或者是对象

normalizeMap(getters).forEach(({ key, val }) => {

// The namespace has been mutated by normalizeNamespace

val = namespace + val

res[key] = function mappedGetter () {

if (namespace && !getModuleByNamespace(this.$store, ‘mapGetters’, namespace)) {

return

}

// 省略代码:匹配不到 getter

return this.$store.getters[val]

}

// mark vuex getter for devtools

res[key].vuex = true

})

return res

})

举例:

computed: {

…mapGetters(‘cart’, {

products: ‘cartProducts’,

total: ‘cartTotalPrice’

})

},

最终转换成:

computed: {

products: this.$store.getters[‘cart/cartProducts’],

total: this.$store.getters[‘cart/cartTotalPrice’],

}

mapActions

创建组件方法分发 action

export const mapActions = normalizeNamespace((namespace, actions) => {

const res = {}

// 省略代码:非生产环境 判断参数 actions 必须是数组或者是对象

normalizeMap(actions).forEach(({ key, val }) => {

res[key] = function mappedAction (…args) {

// get dispatch function from store

最后

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。

因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点!不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
ce, getters) => {

const res = {}

// 省略代码:非生产环境 判断参数 getters 必须是数组或者是对象

normalizeMap(getters).forEach(({ key, val }) => {

// The namespace has been mutated by normalizeNamespace

val = namespace + val

res[key] = function mappedGetter () {

if (namespace && !getModuleByNamespace(this.$store, ‘mapGetters’, namespace)) {

return

}

// 省略代码:匹配不到 getter

return this.$store.getters[val]

}

// mark vuex getter for devtools

res[key].vuex = true

})

return res

})

举例:

computed: {

…mapGetters(‘cart’, {

products: ‘cartProducts’,

total: ‘cartTotalPrice’

})

},

最终转换成:

computed: {

products: this.$store.getters[‘cart/cartProducts’],

total: this.$store.getters[‘cart/cartTotalPrice’],

}

mapActions

创建组件方法分发 action

export const mapActions = normalizeNamespace((namespace, actions) => {

const res = {}

// 省略代码:非生产环境 判断参数 actions 必须是数组或者是对象

normalizeMap(actions).forEach(({ key, val }) => {

res[key] = function mappedAction (…args) {

// get dispatch function from store

最后

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。

因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

[外链图片转存中…(img-sBmPO0S6-1715008739124)]

[外链图片转存中…(img-7li7CII5-1715008739124)]

[外链图片转存中…(img-InSK1Nfl-1715008739125)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点!不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

  • 11
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值