从0开始实现破烂版Vuex

赤裸裸的vuex

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

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

因此咱们先实现一个知足上述条件的简单版vuexvue

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的时候改变数据java



而后,咱们稍稍改造git

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。github

继续改造vuex

dispatch(type,args){
        if(this.actions.hasOwnProperty(type)){
            this.actions[type](this,args)
        }
}复制代码

实现getters

开发中咱们不免会对一些数据进行处理,但咱们又不想每一个页面都实现这样的逻辑,因此vuex又给咱们提供了getter,而且能够像计算属性同样直接访问它。bash

由于咱们想要把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功能都已实现,代码略粗糙,轻喷!ui

实现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其中的原理有个大概的认识。

文章中有什么问题的但愿你们不要吝啬指教,主要以学习交流分享为目的

看到这的小伙伴谢谢你们的支持!!!

相关文章
相关标签/搜索