如何实现一个vuex

249 阅读2分钟
  • 插件的安装:vuex在vue中是第三方插件,通过Vue.use使用
  • 初始化store类
    • 模块的收集(module多个模块的数据的合并)
    • 安装模块(遍历当s所有自模块上的 actions、mutation、getters 都把他定义在父模块上)
    • 将状态和getters 都定义在当前的vm上
    • 插件内部会依次执行
  • 辅助函数的实现

实现入口文件,默认导出Store类和install方法

import { Store, install } from './store'; //


// Vuex.Store  Vuex.install

export default {
    Store,
    install,
}
export * from './helpers';

install和mixin的实现

export default function applyMixin(Vue){
    // 父子组件的beforecreate执行顺序
    Vue.mixin({ 
        beforeCreate:vuexInit,
    });
    
}




function vuexInit(){
    // 给所有的组件增加$store 属性 指向我们创建的store实例
    const options = this.$options; // 获取用户所有的选项
    if(options.store){ // 根实例
        this.$store = options.store;
    }else if(options.parent && options.parent.$store){ // 递归儿子 或者孙子....
        this.$store = options.parent.$store;
    }   
}

初始化store

export class Store {
    constructor(options){
        let state = options.state;
        this._vm = new Vue({
            data:{
                $$state:state,
            }
        });
    }
    get state(){
        return this._vm._data.$$state
    }
}

模块的收集(module多个模块的数据的合并)

this._modules = new ModuleCollection(options);
import Module from './module' 

const forEachValue = (obj,callback) =>{
    Object.keys(obj).forEach(key=>callback(obj[key],key));
}

class ModuleCollection{
    constructor(options){ 
        this.register([],options);
    }
    getNamespaced(path){
        let root = this.root; // 从根模块找起来
        return path.reduce((str,key)=>{
            root = root.getChild(key); 
            return str + (root.namespaced?key + '/' :'' ) 
        },''); 
    }
    register(path,rootModule){  // [a,   c]   [b]
        let newModule = new Module(rootModule)
       
        if(path.length == 0){
            // 根模块
            this.root = newModule; 
        }else{ 
            let parent = path.slice(0,-1).reduce((memo,current)=>{
                return memo.getChild(current)
            },this.root)
            parent.addChild(path[path.length-1],newModule);
        }
        if(rootModule.modules){
            // 循环模块 [a] [b]
            forEachValue(rootModule.modules,(module,moduleName)=>{
                // [a,c]
                this.register(path.concat(moduleName),module)
            })
        }
    }
}

抽离模块类

class Module{
    get namespaced(){
        return !!this._raw.namespaced
    }
    constructor(newModule){
        this._raw = newModule;
        this._children = {};
        this.state = newModule.state
    }
    getChild(key){

        return this._children[key];
    }
    addChild(key,module){
        this._children[key] = module
    }
    // 给模块继续扩展方法
    forEachMutation(fn){
        if(this._raw.mutations){
            forEachValue(this._raw.mutations,fn)
        }
    }
    forEachAction(fn){
        if(this._raw.actions){
            forEachValue(this._raw.actions,fn);
        }
    }
    forEachGetter(fn){
        if(this._raw.getters){
            forEachValue(this._raw.getters,fn);
        }
    }
    forEachChild(fn){
        forEachValue(this._children,fn);
    }
}

安装模块

this._actions = {};
this._wrappedGetters = {};
this._subscribes = [];

installModule(this, state, [], this._modules.root);
const installModule = (store, rootState, path, module) => {
    
    // 这里我需要遍历当前模块上的 actions、mutation、getters 都把他定义在

    // 将所有的子模块的状态module.state安装到父模块的状态上 

    // 我要给当前订阅的事件 增加一个命名空间
    let namespace = store._modules.getNamespaced(path); // 返回前缀即可
    // a/changeAge  b/changeAge   a/c/changeAge   path[a] [b] [a,c]
    if(path.length > 0){ // vuex 可以动态的添加模块
        let parent = path.slice(0,-1).reduce((memo,current)=>{
            return memo[current]
        },rootState)
        console.log(parent,'parent')
       
        Vue.set(parent,path[path.length-1],module.state);
    }
    module.forEachMutation((mutation,key)=>{
        store._mutations[namespace+key] = (store._mutations[namespace+key] || []);
        store._mutations[namespace+key].push((payload)=>{
            mutation.call(store,getState(store,path),payload);
            store._subscribes.forEach(fn=>{
                fn(mutation,store.state); // 用最新的状态 
            })
        })
    })
    module.forEachAction((action,key)=>{
        store._actions[namespace+key] = (store._actions[namespace+key]||[]);
        store._actions[namespace+key].push((payload)=>{
            action.call(store,store,payload)
        })
    })
    module.forEachGetter((getter,key)=>{
        // 模块中getter的名字重复了会覆盖
        store._wrappedGetters[namespace+key] = function () {
            return getter(getState(store,path));
        }
    })
    module.forEachChild((child,key)=>{
        // 递归加载模块
        installModule(store, rootState, path.concat(key), child)
    })
   
}

将状态和getters 都定义在当前的vm上

resetStoreVM(this,state);
function resetStoreVM(store,state){
    const computed = {}; // 定义计算属性
    store.getters = {}; // 定义store中的getters
    forEachValue(store._wrappedGetters,(fn,key)=>{
        computed[key] = ()=>{
            return fn();
        }
        Object.defineProperty(store.getters,key,{
            get:()=>store._vm[key] // 去计算属性中取值
        });
    })
    store._vm = new Vue({ 
        data:{
            $$state:state
        },
        computed // 计算属性有缓存效果
    });
}

插件内部会依次执行

options.plugins.forEach(plugin=>plugin(this));
function persists() {
    return function(store) { // store是当前默认传递的
        let data = localStorage.getItem('VUEX:STATE');
        if (data) {
            store.replaceState(JSON.parse(data));
        }
        store.subscribe((mutation, state) => {
            localStorage.setItem('VUEX:STATE', JSON.stringify(state));
        })
    }
}
plugins: [
    // logger(),  // vuex-persists
    persists()
]

replaceState(state){
    // 替换掉最新的状态
    this._vm._data.$$state = state
}
subscribe(fn){
    this._subscribes.push(fn);
}

实现辅助函数

// 在严格模式下  actions 和 mutations是有区别
commit = (type, payload) => { //保证当前this 当前store实例 
    // 调用commit其实就是去找 刚才绑定的好的mutation
    this._mutations[type].forEach(mutation=>mutation.call(this,payload))
}
dispatch = (type, payload) => {
    this._actions[type].forEach(action=>action.call(this,payload))
}
export function mapState(stateArr) {
    let obj = {};
    for (let i = 0; i < stateArr.length; i++) {
        let stateName = stateArr[i];
        obj[stateName] = function() {
            return this.$store.state[stateName]
        }
    }
    return obj
}
export function mapGetters(gettersArr) {
    let obj = {};
    for (let i = 0; i < gettersArr.length; i++) {
        let gettName = gettersArr[i];
        obj[gettName] = function() {
            return this.$store.getters[gettName]
        }
    }
    return obj
}