从0开始实现破烂版Vuex

1,136 阅读5分钟

赤裸裸的vuex

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。这是官方的定义,言外之意有3层意思。

  1. 全局的数据
  2. 响应式数据
  3. vue的插件

所以我们先实现一个满足上述条件的简单版vuex

class Vuex{
    constructor(modules){
        this.state = modules.state
    }
}
Vuex.install = function(Vue){
    Vue.mixin({
        beforeCreate(){
            let options = this.$options
            //让每个子组件$store都能访问Vuex的store实例
            if(options.store){
                this.$store = options.store
                //让store实例中的state属性变为响应式的
                Vue.util.defineReactive(this.$store,'state')
            }else if(options.parent&&options.parent.store){
                this.$store = options.parent.store
            }
        }
    }
)}
export default Vuex

实现mutations

但是这样的state数据需要通过this.$store.state = "其他值"改变,如果我们要在修改前知道上次的值就要在我们的业务代码里去实现不好管理,所以下一步我们要实现官网推荐的形式,在一个actions的时候改变数据



然后,我们稍稍改造

class Vuex{
    constructor(modules){
        this.modules = modules
        this.state = modules.state
        this.mutations = modules.mutations    }
    commit(type,args){
        console.log('监听提交')
        if(this.mutations.hasOwnProperty(type)){
            this.mutations[type](this.state,args)
        }
        console.log('提交结束')    
    }
}

实现actions

但是我们又会遇到一个问题?commit提交的时候,异步之后修改state的值,会造成commit执行完,但数据还未修改的情况。所以vuex又引入了action的概念,在action中异步提交commit。

继续改造

 dispatch(type,args){
        if(this.actions.hasOwnProperty(type)){
            this.actions[type](this,args)
        }
}

实现getters

开发中我们难免会对一些数据进行处理,但我们又不想每个页面都实现这样的逻辑,所以vuex又给我们提供了getter,并且可以像计算属性一样直接访问它。

因为我们想要把getters改造成计算属性一样的功能,所以我们需要new一个Vue实例,去管理state和getters的数据。

完整代码,如下

class Vuex {
    constructor(modules) {
        this.modules = modules
        this.state = modules.state
        this.mutations = modules.mutations
        this.actions = modules.actions
        this.getters = modules.getters
    }
    commit(type, args) {
        if (this.mutations.hasOwnProperty(type)) {
            this.mutations[type](this.state, args)
        }
    }
    dispatch(type, args) {
        if (this.actions.hasOwnProperty(type)) {
            this.actions[type](this, args)
        }
    }
    
}
Vuex.install = function (Vue) {
    Vue.mixin({
        beforeCreate() {
            let options = this.$options
            if (options.store) {
                const store = this.$store = options.store
                const vm = defineReactive(Vue,store.state,store.getters)
                //访问store中getters数据的时候,代理到vm computed计算属性返回的getter
                defineGetterProperty(store,vm)
            } else if (options.parent && options.parent.store) {
                this.$store = options.parent.store
            }
        }
    })
}
function defineReactive(Vue,state,getters){
    //因为getters中需要传递state参数,所以遍历封装成computed的方法
    let computed = {}
    let getterKeys = Object.keys(getters)
    for(let key of getterKeys){
        computed[key] = function(){
            return getters[key](state)
        }
    }
    //在vue初始化时会把data中定义的数据递归变为响应式的,并实例化一个dep用于依赖收集。
    //所以state中的数据在computed中访问的时候,也会收集computed watcher
    const vm = new Vue({
        data(){
            return {vmState: state}
        },
        computed:{
            ...computed
        }
    })
    return vm
}

function defineGetterProperty(store,vm){
    let getterKeys = Object.keys(store.getters)
    for(let key of getterKeys){
        Object.defineProperty(store,key,{
            get(){
                return vm[key]
            }
        })
    }
}
export default Vuex

简易版的state,mutations,actions,getters功能都已实现,代码略粗糙,轻喷!

实现modules

上面只实现了vuex基础功能,但项目大了以后,所有的数据都定义在一起显的有些臃肿了,所有vuex又引入了modules,使不同的模块管理不同的数据,结构更加清晰。

modules的逻辑比较复杂,没关系,我们分割成一个个小的模块分步去实现它。

实现modules中的state

module中的state是包裹在module对象下的,通过store.模块名.state访问。

因为引入了模块,需要我们递归去收集各个模块中的state,mutations,actions,getters

class Vuex {
    constructor(module) {
        ...
        this.state = Object.create(null)        installModule(module, this)
    }
    ...
}


let nameSpace = []
//递归遍历module收集state,mutations,actions,gettersfunction installModule(module, store) {
    registerState(module, store, nameSpace)
    if (module.modules) {
        for (let name of Object.keys(module.modules)) {
            nameSpace.push(name)
            installModule(module.modules[name], store)
            nameSpace.pop()
        }
    }
}
//收集state的值function registerState(module, store, nameSpace) {
    if (nameSpace.length) {
        //获取父模块,将当前模块中state赋值给父模块命名空间的属性
        let parentModule = getParentModuleState(store.state, nameSpace.slice(0,-1))
        parentModule[nameSpace[nameSpace.length-1]] =  module.state
    } else {
        store.state = module.state
    }
}

function getParentModuleState(state, nameSpace) {
    return nameSpace.reduce((state, name, i) => state[name], state)
}

接下来我们实现mutations的收集

实现modules中的mutations

//收集mutations的方法
function registerMutations(module, mutations){
    let name = nameSpace.length&&module.namespaced ? nameSpace.join('/'):""
    if(module.mutations){
        for(let type of Object.keys(module.mutations)){
            mutations[name+type] = module.mutations[type]
        }
    }
}

但是我们的模块中的mutations,actions,getters的操作一般都是针对当前模块的,所以vuex提供了一些参数去管理当前模块的数据

const moduleA = {
  // ...
  actions: {
    someAction ({ state, commit, dispatch, getters }) {  //这几个参数是适用于当前模块    
    }  }
}

那么去实现一个操作当前模块的参数集合

function makeLocalContext(store){
    let name = nameSpace?nameSpace.join('/')+"/":""
    return {
        state: nameSpace.length ? nameSpace.reduce((state, name) => {
            return state[name]
        }, store.state) : store.state,
        commit: nameSpace.length ? function (type, args) {
            store.commit(name + type, args)
        } : store.commit.bind(store),
        dispatch: nameSpace.length ? function (type, args) {
            store.dispatch(name + type, args)
        } : store.dispatch.bind(store)
    }
}

紧接着我们把actions的功能实现,actions和mutations实现类似,只是需要的当前模块可操作的参数多一点,上面已经定义好,所以直接实现就行了

实现modules中的actions

//收集actions的方法
function registerActions(module, actions,local){
    let name = nameSpace.length&&module.namespaced ? nameSpace.join('/')+"/":""
    if(module.actions){
        for(let type of Object.keys(module.actions)){
            actions[name+type] = function(args){
                module.actions[type](local,args)
            }
        }
    }
}

最后一步我们实现getters

实现modules中的getters

因为getters是计算属性,所以收集的是方法,却直接通过属性名访问

先实现收集过程

//收集getters的方法
function registerGetters(module, getters, local) {
    let path = module.namespaced ? nameSpace.join('/') + "/" : ""
    if (module.getters) {
        for (let type of Object.keys(module.getters)) {
            getters[path + type] = function (args) {
                //这里要把调用的值返回出去,外面才能访问到
                return module.getters[type](local.state, local.getters, args)
            }
        }
    }
}

访问getters中的属性

getters的属性是通过store.getterName  访问,所以我们要把它指向到computed计算属性,上面已经实现过,这里不就粘代码了。但是模块中的getters就需要从store.getters根据命名空间去访问store[namespace + type]

function makeLocalContext(store) {
    let name = nameSpace ? nameSpace.join('/') + "/" : ""
    function defineGetter(){
        const getterTarget = {}
        const getters = nameSpace.reduce((module,name)=>module.modules[name],store.module).getters
        if(getters){
            for (let key of Object.keys(getters)) {
                Object.defineProperty(getterTarget,key,{
                    get(){
                        return store[name+key]
                    }
                })
            }
        }
        return getterTarget
    }
    return {
        ...
        getters: nameSpace.length ? defineGetter() : store
    }
}

完整的代码太长这里就不粘了,github:github.com/13866368297…

到这整个功能终于都实现了,代码有点low,主要希望大家和我一样对vuex其中的原理有个大概的认识。

文章中有什么问题的希望大家不要吝啬指教,主要以学习交流分享为目的

看到这的小伙伴谢谢大家的支持!!!