共计 9574 个字符,预计需要花费 24 分钟才能阅读完成。
介绍
说来惭愧,这对于喜爱摸鱼的程序员来说,vue3 都公布这么久了,当初才开始浏览 vuex3 源码,当初分享一下集体对这次学习的总结,写的不好请大家轻喷。
地址
源码地址:vuejs/vuex@3.6.2
myGithub:2460392754/mini-vuex3
我的项目构造
src | |
├── core | |
│ ├── helpers.ts | |
│ ├── index.ts | |
│ └── store.ts | |
├── types | |
│ └── index.ts | |
├── utils | |
│ ├── index.ts | |
│ ├── isModuleType.ts | |
│ ├── reactive.ts | |
│ └── register.ts | |
└── index.ts |
实现
1 根本
1.1 注册
应用 class
的static
属性进行创立一个名为 install
的静态方法, 用于 Vue.use
注册插件。
插件注册时把以后环境的 vue
对象援用保留下来,确保我的项目和插件应用的是同一个援用对象。因为须要利用我的项目中的 data
性能来辅助 state
和getters
值更新后触发视图更新。
github: mini-vuex3/src/core/store.ts#L6-L23
import type {VueConstructor} from 'vue'; | |
// 保留以后 vue 援用, 确保和我的项目应用雷同的援用 | |
let _vue: VueConstructor; | |
export class store { | |
/** | |
* 插件注册 | |
* @param vue | |
*/ | |
static install(vue: VueConstructor) {_vue = vue;} | |
// do something... | |
} |
1.2 实例化
通过 class
的构造函数进行初始化。
(Tips: registerState
和 registerGetters
会在前面的实例属性里讲到)
github: mini-vuex3/src/core/store.ts#L40-L59
import type Vue from 'vue'; | |
import type {StoreOpts} from '../types'; | |
// 保留以后 vue 援用, 确保和我的项目应用雷同的援用 | |
let _vue: VueConstructor; | |
export class store { | |
private _vm: Vue = null; | |
private _mutations = null; | |
private _actions = null; | |
private _modules = null; | |
getters = null; | |
constructor(opts: StoreOpts) { | |
// 增加 原型属性,指向以后 store 实例化后的对象 | |
_vue.prototype.$store = this; | |
// _state 对象 响应式解决, 须要告诉我的项目视图更新 | |
this._vm = new _vue({data() { | |
return {_state: registerState(opts), | |
}; | |
}, | |
}); | |
this.getters = registerGetters(this.state, opts || {}); | |
this._mutations = opts.mutations || {}; | |
this._actions = opts.actions || {}; | |
this._modules = opts.modules || {}; | |
registerModules(this._mutations, this._actions, this._modules); | |
} | |
} |
registerModules
应用 es6 中 Reflect
个性进行批改对象的键名模仿公有对象, 例如: {A: 'a'}
=> {_A: 'a'}
。
把规范化后 moduels.[NAME].mutations
和moduels.[NAME].actions
对象进行合并。
github: mini-vuex3/src/utils/register.ts#L57-L84
/** | |
* 注册 modules | |
*/ | |
export function registerModules(mutations: Mutations, actions: Actions, modules: Modules) {Object.keys(modules).forEach((key) => {const module = modules[key] as Module & { | |
_actions: Actions; | |
_mutations: Mutations; | |
}; | |
// 批改键名 | |
Reflect.set(module, '_actions', module.actions); | |
Reflect.set(module, '_mutations', module.mutations); | |
Reflect.deleteProperty(module, 'actions'); | |
Reflect.deleteProperty(module, 'mutations'); | |
let moduleActions = module._actions; | |
let moduleMutations = module._mutations; | |
if (module.namespaced === true) {moduleMutations = setModuleNameDataKey(module.name, moduleMutations); | |
moduleActions = setModuleNameDataKey(module.name, moduleActions); | |
} | |
Object.assign(mutations, moduleMutations); | |
Object.assign(actions, moduleActions); | |
}); | |
} | |
/** | |
* 批改 modules 中对象的键名, 应用 module.name 追加拼接 | |
* @param moduleName | |
* @param data | |
* @returns | |
*/ | |
function setModuleNameDataKey(moduleName: string, data: { [key: string]: Function }) {const res = {}; | |
Object.keys(data).forEach((key) => { | |
const newKey = moduleName + '/' + key; | |
res[newKey] = data[key]; | |
}); | |
return res; | |
} |
2 实例属性
2.1 state 属性
github: mini-vuex3/src/core/store.ts#L28-L51
export class store { | |
/** | |
* 设置 state 的 get 拜访器 | |
*/ | |
get state(): State {return this._vm.$data._state;} | |
/** | |
* 设置 state 的 set 拜访器 | |
* 禁止间接写入数据 | |
*/ | |
set state(v: any) {throw new Error("can't set state: " + v); | |
} | |
constructor(opts: StoreOpts) { | |
// _state 对象 响应式解决, 须要告诉我的项目视图更新 | |
this._vm = new _vue({data() { | |
return {_state: registerState(opts), | |
}; | |
}, | |
}); | |
// do something... | |
} | |
// do something... | |
} |
registerState
把 module.[NAME].state
合并到 state
中
github: mini-vuex3/src/utils/register.ts#L22-L35
/** | |
* 注册 state | |
* @param opts | |
* @returns | |
*/ | |
export function registerState(opts: StoreOpts) {const moduleStates = {}; | |
Object.values(opts.modules || {}).forEach((module) => {moduleStates[module.name] = module.state || {};}); | |
return Object.assign(moduleStates, opts.state || {}); | |
} |
2.2 getters 属性
github: mini-vuex3/src/core/store.ts#L53
export class store {constructor(opts: StoreOpts) {this.getters = registerGetters(this.state, opts || {}); | |
// do something... | |
} | |
// do something... | |
} |
registerGetters
把 module.[NAME].getters
合并到 getters
中, 并通过 Object.defineProperty
劫持 getters
中所有对象,转换成响应式类型。
github: mini-vuex3/src/utils/register.ts#L37-L55
/** | |
* 注册 getters | |
*/ | |
export function registerGetters(state: State, opts: StoreOpts) {const getters: any = {}; | |
reactiveGetters(opts.getters, state, getters); | |
Object.values(opts.modules || {}).forEach((module) => {if (module.namespaced === true) {const newGetters = setModuleNameDataKey(module.name, module.getters); | |
reactiveGetters(newGetters, state[module.name], getters); | |
} else {reactiveGetters(module.getters, state[module.name], getters); | |
} | |
}); | |
return getters; | |
} | |
/** | |
* 劫持 getters 对象,解决成响应式内容 | |
* @param getters | |
* @param state | |
* @param res | |
*/ | |
export function reactiveGetters(getters: Getters, state: State, res: { [key: string]: string }) {for (let key in getters) { | |
Object.defineProperty(res, key, {get: () => {return getters[key](state); | |
}, | |
set(key) {console.error(`Cannot set getters ${key}`); | |
}, | |
}); | |
} | |
} |
3 实例办法
3.1 commit 办法
拜访 namespaced
为true
的 modules
对象时, 批改 state
对象范畴。
github: mini-vuex3/src/core/store.ts#L61-L85
export class store { | |
// do something... | |
commit(type: string, payload: Payload) {const func: Mutation = this._mutations[type]; | |
let state: State; | |
// 未定义属性 | |
if (typeof func === 'undefined') {throw new Error(`unknown mutation type: ${type}`); | |
} | |
if (isModuleType(type)) {const name = type.split('/')[0]; | |
const module = this._modules[name]; | |
state = module.state; | |
} else {state = this.state;} | |
func.call(this, state, payload); | |
} | |
} |
3.2 dispatch 办法
拜访 namespaced
为true
的 modules
对象时, 批改函数内 this
的commit
和 dispatch
作用域范畴和对应的 store
内容。
github: mini-vuex3/src/core/store.ts#L87-L116
export class store { | |
// do something... | |
dispatch(type: string, payload: Payload) {const func: Action = this._actions[type]; | |
let store: any; | |
// 未定义属性 | |
if (typeof func === 'undefined') {throw new Error(`unknown action type: ${type}`); | |
} | |
if (isModuleType(type)) {const name = type.split('/')[0]; | |
const module = this._modules[name]; | |
store = module; | |
// 批改作用域范畴 | |
Object.assign(store, {commit: this.commit.bind(store), | |
dispatch: this.dispatch.bind(store), | |
}); | |
} else {store = this;} | |
func.call(this, store, payload); | |
} | |
} |
4 辅助函数
4.1 mapState 办法
github: mini-vuex3/src/core/helpers.ts#L52-L92
/** | |
* 辅助工具 mapState | |
* @returns | |
*/ | |
export function mapState() {const { moduleName, opts} = normalizeNamespace(arguments[0], arguments[1]); | |
const resFunc = {}; | |
// 数组内容,例如:mapState(['x1', 'x2']) 或 mapState('xxxModule', ['xxx1', 'xxx2']) | |
if (Array.isArray(opts)) {opts.forEach((stateKey) => {resFunc[stateKey] = function () {return handleModuleType.call(this, moduleName, 'state')[stateKey]; | |
}; | |
}); | |
} | |
// 解决对象构造 | |
else {for (const [newStateKey, val] of Object.entries<string | Function>(opts)) {// mapState({ xxFunc: (state) => state.xx1 }) 或 mapState({xxFunc(state){return state.xx1 + this.xxx1} }) | |
if (typeof val === 'function') {resFunc[newStateKey] = function () {const state = handleModuleType.call(this, moduleName, 'state'); | |
// 批改 this 指向,解决 回调函数中应用以后 vm 实例中的 data 或 computed 变量 | |
return val.call(this, state); | |
}; | |
} | |
// mapState({xxxxxxx1: 'x1'}) 或 mapState('xxxModule', { xxxxxxx1: 'x1'}) | |
else {resFunc[newStateKey] = function () {return handleModuleType.call(this, moduleName, 'state')[val]; | |
}; | |
} | |
} | |
} | |
return resFunc; | |
} | |
/** | |
* 格式化 参数(命名空间和数据)* @param namespace | |
* @param map | |
* @returns | |
*/ | |
function normalizeNamespace(moduleName: string, opts: any) { | |
// 未定义 moduleName | |
if (typeof moduleName !== 'string') { | |
return { | |
moduleName: null, | |
opts: moduleName, | |
}; | |
} | |
return { | |
moduleName, | |
opts, | |
}; | |
} | |
/** | |
* 解决命名空间(module 类型)* @param moduleName | |
*/ | |
function handleModuleType(moduleName: string | null, type: string, key: string | undefined) {if (type === 'state') {return moduleName === null ? this.$store[type] : this.$store[type][moduleName]; | |
} | |
if (key === undefined) {return this.$store[type]; | |
} | |
let newKey = key; | |
if (moduleName !== null) {newKey = moduleName + '/' + key;} | |
return this.$store[type][newKey]; | |
} |
4.2 mapGetters 办法
github: mini-vuex3/src/core/helpers.ts#L94-L119
/** | |
* 辅助工具 mapGetters | |
* @returns | |
*/ | |
export function mapGetters() {const { moduleName, opts} = normalizeNamespace(arguments[0], arguments[1]); | |
const resFunc = {}; | |
// 数组内容,例如:mapGetters(['x1', 'x2']) 或 mapGetters('xxxModule', ['xxx1', 'xxx2']) | |
if (Array.isArray(opts)) {opts.forEach((getterKey) => {resFunc[getterKey] = function () {return handleModuleType.call(this, moduleName, 'getters', getterKey); | |
}; | |
}); | |
} else {// mapGetters({ xxxxxxx1: 'x1'}) 或 mapGetters('xxxModule', { xxxxxxx1: 'x1'}) | |
for (const [newGetterKey, oldGetterKey] of Object.entries<string>(opts)) {resFunc[newGetterKey] = function () {return handleModuleType.call(this, moduleName, 'getters', oldGetterKey); | |
}; | |
} | |
} | |
return resFunc; | |
} |
4.3 mapMutations 办法
github: mini-vuex3/src/core/helpers.ts#L121-L152
/** | |
* 辅助工具 mapMutations | |
* @returns | |
*/ | |
export function mapMutations() {const { moduleName, opts} = normalizeNamespace(arguments[0], arguments[1]); | |
const resFunc = {}; | |
// 数组内容,例如:mapMutations(['x1', 'x2']) 或 mapMutations('xxxModule', ['xxx1', 'xxx2']) | |
if (Array.isArray(opts)) {opts.forEach((getterKey) => {resFunc[getterKey] = function (payload) {const func = handleModuleType.call(this, moduleName, '_mutations', getterKey); | |
const state = handleModuleType.call(this, moduleName, 'state'); | |
return func(state, payload); | |
}; | |
}); | |
} else {for (const [newGetterKey, oldGetterKey] of Object.entries<string>(opts)) {// mapMutations({ xxxxxxx1: 'x1'}) 或 mapMutations('xxxModule', { xxxxxxx1: 'x1'}) | |
resFunc[newGetterKey] = function (payload) {const func = handleModuleType.call(this, moduleName, '_mutations', oldGetterKey); | |
const state = handleModuleType.call(this, moduleName, 'state'); | |
return func(state, payload); | |
}; | |
} | |
} | |
return resFunc; | |
} |
4.4 mapActions 办法
github: mini-vuex3/src/core/helpers.ts#L154-L200
/** | |
* 辅助工具 mapActions | |
* @returns | |
*/ | |
export function mapActions() {const { moduleName, opts} = normalizeNamespace(arguments[0], arguments[1]); | |
const resFunc = {}; | |
// 数组内容,例如:mapActions(['x1', 'x2']) 或 mapActions('xxxModule', ['xxx1', 'xxx2']) | |
if (Array.isArray(opts)) {opts.forEach((getterKey) => {resFunc[getterKey] = function (payload) {const func = handleModuleType.call(this, moduleName, '_actions', getterKey); | |
let store = handleModuleStore.call(this, moduleName); | |
store = Object.assign({ ...store}, | |
{commit: this.$store.commit.bind(store), | |
dispatch: this.$store.dispatch.bind(store), | |
} | |
); | |
return func(store, payload); | |
}; | |
}); | |
} else {for (const [newGetterKey, oldGetterKey] of Object.entries<string>(opts)) {// mapActions({ xxxxxxx1: 'x1'}) 或 mapActions('xxxModule', { xxxxxxx1: 'x1'}) | |
resFunc[newGetterKey] = function (payload) {let store = handleModuleStore.call(this, moduleName, '_actions', oldGetterKey); | |
store = Object.assign({ ...store}, | |
{commit: this.$store.commit.bind(store), | |
dispatch: this.$store.dispatch.bind(store), | |
} | |
); | |
console.log(store); | |
return store['_actions'][oldGetterKey](store, payload); | |
}; | |
} | |
} | |
return resFunc; | |
} |
最初
手写一个 mini-vuex3 相对来说还是比拟容易的,还是须要多看、多学、多写。
# 地址