console.log(‘vm. s t o r e = = = v m . store === vm. store===vm.children[0]. c h i l d r e n [ 1 ] . children[1]. children[1].store’, vm. s t o r e = = = v m . store === vm. store===vm.children[0]. c h i l d r e n [ 1 ] . children[1]. children[1].store)
// true
Vuex.Store 构造函数
先看最终 new Vuex.Store
之后的 Store
实例对象关系图:先大致有个印象。
export class Store {
constructor (options = {}) {
// 这个构造函数比较长,这里省略,后文分开细述
}
}
if (!Vue && typeof window !== ‘undefined’ && window.Vue) {
install(window.Vue)
}
如果是 cdn script
方式引入vuex
插件,则自动安装vuex
插件,不需要用Vue.use(Vuex)
来安装。
// asset 函数实现
export function assert (condition, msg) {
if (!condition) throw new Error([vuex] ${msg}
)
}
if (process.env.NODE_ENV !== ‘production’) {
// 可能有读者会问:为啥不用 console.assert,console.assert 函数报错不会阻止后续代码执行
assert(Vue, must call Vue.use(Vuex) before creating a store instance.
)
assert(typeof Promise !== ‘undefined’, vuex requires a Promise polyfill in this browser.
)
assert(this instanceof Store, store must be called with the new operator.
)
}
条件断言:不满足直接抛出错误
1.必须使用
Vue.use(Vuex)
创建store
实例。
2.当前环境不支持
Promise
,报错:vuex
需要Promise polyfill
。
3.
Store
函数必须使用new
操作符调用。
const {
// 插件默认是空数组
plugins = [],
// 严格模式默认是false
strict = false
} = options
从用户定义的new Vuex.Store(options)
取出plugins
和strict
参数。
// store internal state
// store 实例对象 内部的 state
this._committing = false
// 用来存放处理后的用户自定义的actoins
this._actions = Object.create(null)
// 用来存放 actions 订阅
this._actionSubscribers = []
// 用来存放处理后的用户自定义的mutations
this._mutations = Object.create(null)
// 用来存放处理后的用户自定义的 getters
this._wrappedGetters = Object.create(null)
// 模块收集器,构造模块树形结构
this._modules = new ModuleCollection(options)
// 用于存储模块命名空间的关系
this._modulesNamespaceMap = Object.create(null)
// 订阅
this._subscribers = []
// 用于使用 $watch 观测 getters
this._watcherVM = new Vue()
// 用来存放生成的本地 getters 的缓存
this._makeLocalGettersCache = Object.create(null)
声明Store
实例对象一些内部变量。用于存放处理后用户自定义的actions
、mutations
、getters
等变量。
提一下
Object.create(null)
和{}
的区别。前者没有原型链,后者有。即Object.create(null).__proto__
是undefined
({}).__proto__
是Object.prototype
// bind commit and dispatch to self
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)
}
给自己 绑定 commit
和 dispatch
为何要这样绑定 ?
说明调用
commit
和dispach
的this
不一定是store
实例
这是确保这两个函数里的
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)
,如果暂时不想看,可以直接看返回结果。installModule
,resetStoreVM
函数则可以断点调试。
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
中mapState
、mapGetters
、mapMutations
、mapActions
四个辅助函数使用的。
生成本地的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
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数同学面临毕业设计项目选题时,很多人都会感到无从下手,尤其是对于计算机专业的学生来说,选择一个合适的题目尤为重要。因为毕业设计不仅是我们在大学四年学习的一个总结,更是展示自己能力的重要机会。
因此收集整理了一份《2024年计算机毕业设计项目大全》,初衷也很简单,就是希望能够帮助提高效率,同时减轻大家的负担。
既有Java、Web、PHP、也有C、小程序、Python等项目供你选择,真正体系化!
由于项目比较多,这里只是将部分目录截图出来,每个节点里面都包含素材文档、项目源码、讲解视频
如果你觉得这些内容对你有帮助,可以添加VX:vip1024c (备注项目大全获取)
wOptions)
// 重置 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
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数同学面临毕业设计项目选题时,很多人都会感到无从下手,尤其是对于计算机专业的学生来说,选择一个合适的题目尤为重要。因为毕业设计不仅是我们在大学四年学习的一个总结,更是展示自己能力的重要机会。
因此收集整理了一份《2024年计算机毕业设计项目大全》,初衷也很简单,就是希望能够帮助提高效率,同时减轻大家的负担。
[外链图片转存中…(img-sHFAJQ0P-1712538418579)]
[外链图片转存中…(img-a9z1Hh1W-1712538418579)]
[外链图片转存中…(img-yAtoWebR-1712538418580)]
既有Java、Web、PHP、也有C、小程序、Python等项目供你选择,真正体系化!
由于项目比较多,这里只是将部分目录截图出来,每个节点里面都包含素材文档、项目源码、讲解视频
如果你觉得这些内容对你有帮助,可以添加VX:vip1024c (备注项目大全获取)
[外链图片转存中…(img-7FnU7csR-1712538418580)]