Vuex简单实现

Vuex简单实现

1. 实现this.$store全局访问

const install = (vue) => {
    Vue = vue;
    Vue.mixin({
        beforeCreate() {
            /* 获取根组件传递$store */
            if (this.$options && this.$options.store) {
                this.$store = this.$options.store;
            } else {/* 从父组件传递 */
                this.$store = this.$parent && this.$parent.$store;
            }
        }
    })
}
export default { install, Store }//导出

Vue.use(vuex)的过程中会调用vuexinstall方法,同时把Vue作为形参传入,通过Vue.mixin给所有的组件添加生命周期事件,先给根组件设置this.$store = this.$options.store,然后让所有子组件继承

2.Store类的实现

class Store {
    constructor(options) {
        /* 初始化 */
        this.vm = new Vue({
            data: { state: options.state }
        })/* 使state里的数据响应式化 */
        this.getters = {};
        this.mutations = {};
        this.actions = {};
        /* 将modules转换格式 _raw _children state */
        this.modules = new ModuleCollection(options);
        console.log(this.modules);
        /* 安装模块 */
        installModules(this, this.state, [], this.modules.root);
    }
    get state() {/* 代理获取state */
        return this.vm.state;
    }
    commit = (key, ...payload) => {
        this.mutations[key].forEach(fn => fn(...payload));
    }
    dispatch = (key, ...payload) => {
        this.actions[key].forEach(fn => fn(...payload));
    }
    registerModule(moduleName, module) {
        /* 注册模块 */
        this.modules.register([moduleName], module);/* (path,rootModule) */
        /* 格式化后的模块 */
        let rawModule = this.modules.root._children[moduleName];
        installModules(this, this.state, [moduleName], rawModule);
    }
}

1.将state放到一个vue实例中,实现响应式,同时通过get state()实现代理
2.初始化getters,mutations,actions
3.创建一个ModuleColeection对象,将modules转化为标准格式

class ModuleCollection {
    constructor(options) {
        this.register([], options)
    }
    /* path记录根到当前模块路径[b,c] */
    register(path, rootModule) {
        /* 创建当前模块的格式化对象 */
        let rawModule = {
            _raw: rootModule,
            _children: {},
            state: rootModule.state
        }
        /* 若还没有根,第一次进入,则给根模块赋值 */
        if (!this.root) {
            this.root = rawModule;
        } else {
            /* 找到当前模块父模块 [b,c] => this.root._children['b'] */
            let parent = path.slice(0, -1).reduce((data, item) => {
                return data._children[item];
            }, this.root);
            /* 示例:this.root._children['b']._children['c']=rawModule */
            parent._children[path[path.length - 1]] = rawModule;
        }
        /* 遍历注册子模块 */
        if (rootModule.modules) {
            forEachValue(rootModule.modules, (moduleName, module) => {
                this.register(path.concat(moduleName), module);
            })
        }
    }
}

ModuleColeection每次调用register方法都会创建一个对象rawModule,将每个模块的所有内容放到_raw中,将state数据放到state中,用_children来模块的直接子模块,第一次调用register时将options转化成的rawModule赋给this.root

/* 创建当前模块的格式化对象 */
let rawModule = {
    _raw: rootModule,
    _children: {},
    state: rootModule.state
}

利用封装的全局方法forEachValue取得子模块的名字和内容,递归调用子模块进行注册

let forEachValue = (obj, callback) => {
    Object.keys(obj).forEach(key => {
        callback(key, obj[key]);
    })
}
/* 遍历注册子模块 */
if (rootModule.modules) {
    forEachValue(rootModule.modules, (moduleName, module) => {
        this.register(path.concat(moduleName), module);
    })
}

path.concat的作用是记录路径,用于找到父模块,将自身放到父模块的_children对象中,形成图中格式,例如下面代码:在不是根模块的情况下,register传入的path=['b','c']时,就可以推断c模块属于第三层,通过前面的b找到父模块,再将自己放到父模块的_children对象。

/* 找到当前模块父模块 [b,c] => this.root._children['b'] */
let parent = path.slice(0, -1).reduce((data, item) => {
    return data._children[item];
}, this.root);
/* 示例:this.root._children['b']._children['c']=rawModule */
parent._children[path[path.length - 1]] = rawModule;

格式化后的形式
4. installModules安装模块

function installModules(store, rootState, path, rawModule) {
    /* 把所有数据放到state上 */
    if (path.length) {
        /* 获取父模块 示例:['b','c'] => rootState['b'] */
        let parent = path.slice(0, -1).reduce((data, item) => {
            return data[item];
        }, rootState);
        /* rootState['b']['c'] = rawModule.state */
        Vue.set(parent, path[path.length - 1], rawModule.state);
    }
    /* getters */
    let getters = rawModule._raw.getters;
    if (getters) {
        forEachValue(getters, (key, value) => {
            Object.defineProperty(store.getters, key, {
                get: () => value(rawModule.state)
            })
        })
    }
    /* mutations */
    let mutations = rawModule._raw.mutations;
    if (mutations) {
        forEachValue(mutations, (mutationName, value) => {
            /* 收集所有模块的同名mutation */
            let arr = store.mutations[mutationName] || (store.mutations[mutationName] = []);
            arr.push((...payload) => { value(rawModule.state, ...payload) });
        })
    }
    /* actions */
    let actions = rawModule._raw.actions;
    if (actions) {
        forEachValue(actions, (actionName, value) => {
            let arr = store.actions[actionName] || (store.actions[actionName] = []);
            arr.push((...payload) => { value(store, ...payload) });
        })
    }
    /* 遍历子模块 */
    forEachValue(rawModule._children, (name, value) => {
        installModules(store, rootState, path.concat(name), value);
    })
}

实现在组件中类似{{$store.state.b.c.num}}的调用方式,原理就是将所有的数据根据嵌套关系放到state中,利用reduce寻找父模块,调用Vue.set添加响应式数据

/* 把所有数据放到state上 */
if (path.length) {
    /* 获取父模块 示例:['b','c'] => rootState['b'] */
    let parent = path.slice(0, -1).reduce((data, item) => {
        return data[item];
    }, rootState);
    /* rootState['b']['c'] = rawModule.state */
    Vue.set(parent, path[path.length - 1], rawModule.state);
}

对所有模块的getters进行劫持,直接使用$store.getters访问,限制就是模块之间命名不能重复

/* getters */
let getters = rawModule._raw.getters;
if (getters) {
    forEachValue(getters, (key, value) => {
        Object.defineProperty(store.getters, key, {
            get: () => value(rawModule.state)//执行函数返回结果
        })
    })
}

将所有模块的mutations成员放到store.mutations对象上去,然后根据名称划分为多个订阅数组,commit调用时就可以直接触发所有模块执行同名的函数,actions区别在于传回的第一个参数时store,这样做的原因是实现actions到达事件后可以调用mutations成员执行操作

/* ------------installMutations------------ */
/* mutations */
let mutations = rawModule._raw.mutations;
if (mutations) {
    forEachValue(mutations, (mutationName, value) => {
        /* 收集所有模块的同名mutation */
        let arr = store.mutations[mutationName] || (store.mutations[mutationName] = []);
        arr.push((...payload) => { value(rawModule.state, ...payload) });
    })
}
/* actions */
let actions = rawModule._raw.actions;
if (actions) {
    forEachValue(actions, (actionName, value) => {
        let arr = store.actions[actionName] || (store.actions[actionName] = []);
        arr.push((...payload) => { value(store, ...payload) });
    })
}
/* -------------------Store------------------- */
commit = (key, ...payload) => {
    this.mutations[key].forEach(fn => fn(...payload));
}
dispatch = (key, ...payload) => {
    this.actions[key].forEach(fn => fn(...payload));
}

递归安装子模块

/* 遍历子模块 */
forEachValue(rawModule._children, (name, value) => {
    installModules(store, rootState, path.concat(name), value);
})

5.registerModule方法拓展模块

/* ------------Store方法------------ */
registerModule(moduleName, module) {
    /* 注册模块 */
    this.modules.register([moduleName], module);/* (path,rootModule) */
    /* 格式化后的模块 */
    let rawModule = this.modules.root._children[moduleName];
    installModules(this, this.state, [moduleName], rawModule);
}

/* -----------外部调用方式----------- */
/* 增加组件 */
store.registerModule('d', {
    state: { num: 'd1' },
    modules: {
        e: { state: { num: 'e1' } }
    }
})

源码:https://gitee.com/aeipyuan/my_vuex

posted @ 2020-04-22 18:23  aeipyuan  阅读(361)  评论(0编辑  收藏  举报