介绍

说来惭愧,这对于喜爱摸鱼的程序员来说,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 注册

应用classstatic属性进行创立一个名为install的静态方法, 用于Vue.use注册插件。

插件注册时把以后环境的vue对象援用保留下来,确保我的项目和插件应用的是同一个援用对象。因为须要利用我的项目中的data性能来辅助stategetters值更新后触发视图更新。

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: registerStateregisterGetters会在前面的实例属性里讲到)

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].mutationsmoduels.[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 办法

拜访namespacedtruemodules对象时, 批改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 办法

拜访namespacedtruemodules对象时, 批改函数内thiscommitdispatch作用域范畴和对应的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 相对来说还是比拟容易的,还是须要多看、多学、多写。

#地址