從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其中的原理有個大概的認識。

文章中有什麼問題的但願你們不要吝嗇指教,主要以學習交流分享爲目的

看到這的小夥伴謝謝你們的支持!!!

相關文章
相關標籤/搜索