vuex的源碼一些理解

學習Vue有一段時間了,感受對於Vuex老是停留在只知其一;不知其二的狀態,決定花點時間好好學習研究下Vuex的實現。Vuex的設計思想,借鑑了Redux將數據存放到全局的store,再將store掛載到每一個vue實例組件中,利用Vue.js的數據響應機制來進行高效的派發和狀態更新。javascript

開始前的準備

我的以爲有必要理解這幾個知識點對於理解源碼有很大的幫助vue

  • reduce函數的做用
  • Object.defineProperty和Object.defineProperties的做用
  • Object.create的做用
  • 閉包
  • 原型鏈,構造函數
  • 引用類型和值類型

基本使用方法

舉個例子,假設該模塊須要命名空間,根據例子再去摸索源碼會有更加不錯的幫助java

store/user.jsvuex

modules: {
    users: {
        namespaced: true,
        state: {
            username: null,
        },
        mutations: {
            SET_USER(state, payload) {
                state.username = payload
            }
        },
        actions: {
            // context包含commit, dispatch, localState, localGetters, rootGetters, rootState
            FETCH_USER(context, payload) {
            }
        },
        getters: {
            GET_USER(localState, localGetters, rootState, rootGetters) {
                return localState.username
            }
        }
    }
}

store/index.js數組

import Vue from 'vue'
import Vuex from 'vuex'
import user from './user'

Vue.use(vuex);

new Store({
    modules: {
        user
    }
})

user.vue閉包

<script>
    import { mapGetters, mapActions, mapMutations } from 'vuex';
    export default {
        computed: {
            ...mapGetters('user', [
                'GET_USER'
            ])
        },
        methods: {
            ...mapActions('user', {
                'fetchUser': FETCH_USER,
            }),
            ...mapMutations('user', {
                'setUser': SET_USER,
            }),
            loginByUsername() {
                // fetchUser請求
            },
            loginByDispatch() {
                this.$store.dispatch('user/FETCH_USER', {
                    user: ...,
                    password: ....,
                    randomStr: ....,
                    code: ...
                }).then(res => console.log(res))
                .catch(err => console.log(err))
                .finally()
            }
        }
    }
</script>

new Store

主要完成了對於一些狀態的初始化,_mutations對象將用於存放模塊中的全部mutations, _actions對象將用於存放模塊中的全部actions,_wrappedGetters用於存放模塊中的全部getter, _modulesNamespaceMap用於存放存在namespaced爲true的key-value表,對於module對象進行從新註冊:app

// rawRootModule爲傳入Store中的原生module對象
var ModuleCollection = function ModuleCollection (rawRootModule) {
  // register root module (Vuex.Store options)
  this.register([], rawRootModule, false);
};
ModuleCollection.prototype.register = function register (path, rawModule, runtime) {
  var this$1 = this;
  ..
  var newModule = new Module(rawModule, runtime);
  if (path.length === 0) {
    this.root = newModule;
  } else {
    var parent = this.get(path.slice(0, -1));
    parent.addChild(path[path.length - 1], newModule);
  }

  // register nested modules
  if (rawModule.modules) {
    forEachValue(rawModule.modules, function (rawChildModule, key) {
      this$1.register(path.concat(key), rawChildModule, runtime);
    });
  }
};

register

  • 註冊函數主要完成兩個事情:dom

    • 1.構造module對象
    • 2.判斷是否有子模塊,若是有則繼續進行遍歷

1. 構造module對象

// Base data struct for store's module, package with some attribute and method
var Module = function Module (rawModule, runtime) {
  this.runtime = runtime;
  // Store some children item
  this._children = Object.create(null);
  // Store the origin module object which passed by programmer
  this._rawModule = rawModule;
  var rawState = rawModule.state;

  // Store the origin module's state
  this.state = (typeof rawState === 'function' ? rawState() : rawState) || {};
};

2. 判斷是否有子模塊,若是有則繼續進行遍歷

modules: {
    user: {
        state: {level: 1}
        post: {
            state: {level: 2}
        }
    }
}

首先初始化path長度爲0,最外層構造出來的module對象即爲root, 而後因爲存在子模會將user模塊add到root下的_children,結果爲函數

root: {
    ...
    _children: {
        user module
    }
}

而後經過判斷存在子模塊,則繼續進行遞歸遍歷,此時因爲上一層的函數沒有出棧,經過path.concat(key), path爲['user', 'post'],經過ModuleCollection原型中的get來獲取當前模塊的父模塊post

// result: ['user']
let parentPath = ['user', 'post'].slice(0, -1);
// root爲根模塊,最終獲取到的爲user module
parentPath.reduce((module, key) => module._children, root)
// 將新增模塊加入到user module
root: {
    ...
    _children: {
        user: {
            _children: {
                post module
            }
        }
    }
}

最終構形成以下

installModule

完成了模塊的註冊之後,最重要的一句代碼是installModule, 該方法顧名思義就是將注入的modules進行內部的組裝, 若是存在子模塊經過遞歸的方法來獲取,而且合併path路徑。

首先模塊分爲有命名空間和沒有命名空間兩塊,經過getNamespace判斷獲取命名空間,好比path = ['user', 'post', 'report'], 經過reduce方法首先經過getChild獲取root下的_children裏的user模塊, 若是namespaced爲true,則加上路徑,這樣一層層下去,全部的模塊,子模塊內部將會造成相似user,user/post/形式的命名空間。

ModuleCollection.prototype.getNamespace = function getNamespace (path) {
  var module = this.root;
  return path.reduce(function (namespace, key) {
    // 獲取子模塊
    module = module.getChild(key);
    return namespace + (module.namespaced ? key + '/' : '')
  }, '')
};

命名空間很重要,以後commit,dispatch, mapMutations, mapActions等一些列操做都是基於此。以後將子module下全部的state所有暴露到根節點下的state,經過使用vue.set將新的屬性設置到rootState上, 這個state將來將被用於store.state獲取其狀態

// set state
if (!isRoot && !hot) {
  // 經過path.slice(0, -1)來截取當前module以前的全部父類路徑,經過reduce來獲取當前模塊上一級的父模塊
  var parentState = getNestedState(rootState, path.slice(0, -1));
  var moduleName = path[path.length - 1];
  store._withCommit(function () {
    Vue.set(parentState, moduleName, module.state);
  });
}

以後是將註冊registerMutation,registerAction,registerGetter,在註冊以前,vuex作了巧妙的處理,動態設置當前模塊所在的環境

var local = module.context = makeLocalContext(store, namespace, path);
local = {
    dispatch,
    commit,
    // getters and state object must be gotten lazily
    // because they will be changed by vm update
    Object.defineProperties(local, {
      getters: {
      get: noNamespace
        ? function () { return store.getters; }
        : function () { return makeLocalGetters(store, namespace); }
      },
      state: {
        get: function () { return getNestedState(store.state, path); }
      }
    });
}

經過namespaced來判斷設置當前環境下local對象內的dispatch,commit, 若是存在就在dispatch和commit內部加上namespaced前綴,此外還加入了了local.state和local.getter,經過Object.defineProperties來設置訪問器屬性,當不一樣模塊內部好比actions,mutations或者getters中的方法進行獲取的時候會進行動態獲取。好比帶有命名空間的模塊:

{
    user: {
        namspaced: true
        state: {
            username: 1
        }
        mutations: {
            SET_USER(state, payload) {}
        },
        actions: {
            FETCH_USER({dispatch, commit, getters, state, rootGetters, rootState}, payload) {
                // ...
            }
        },
        getters: {
            GET_USER() {}
        }
    }
}
  • registerMutation

    就是將全部模塊內部的mutations平鋪到_mutations中造成key-value的鍵值對,key爲namespaced+key。當觸發方法的時候會內置local.state,能夠在方法的第一個參數獲取到內部本身的state

    上面例子最後會被平鋪成

    _mutations: {
        'user/SET_USER': [wrappedMutationHandler]
    }

    當commit('SET_USER', 1)的時候SET_USER的參數第一個參數會去動態獲取state的值, 具體獲取方式是經過getNestedState方法,配合path來獲取其state。

    // 例以下面例子,經過reduce首先獲取root層,再次遍歷獲取user層對象數據
    path: ['root', 'user']
    store.state:
    {
        root: {
            ...
            user:{
            }
        }
    }
  • registerAction

    相似於註冊mutation,會將全部模塊下的actions平鋪到_actions, 上面例子最後會平鋪成

    _actions: {
        'user/FETCH_USER': [wrappedActionHandler]
    }

    因此外部進行dispatch的時候,若是有命名空間須要加上,例如store.dispatch('user/GET_USER',1),內部其實經過key找到_actions內部的entry,而後調用wrappedActionHandler(payload),當觸發方法的時候內部一樣內置了local.dispatch,local.commmit, local.state,local.getters,store.getters, store.state.

    • 其中內置的dispatch和commit若是存在namespaced能夠直接經過方法名進行提交,由於環境下已經配置好了,將namespace組合好了。
    • local.state是訪問器屬性,同註冊mutation。
    • local.getters須要說起一下,當存在命名空間的時候,例如當例子GET_USER方法獲取getters的時候,能夠直接經過getters['GET_USER'], 他內部設置了代理getter,1.首先遍歷最外層全部的getters;2.獲取namespace命名空間長度,截取以後的字符串,若是長度爲0則仍舊截取全部;3.設置訪問器屬性,屬性名字爲截取掉的type;4.當方法內進行調用的時候就會調用get方法動態獲取其getter。

      function makeLocalGetters (store, namespace) {
        var gettersProxy = {};
      
        var splitPos = namespace.length;
        Object.keys(store.getters).forEach(function (type) {
          // skip if the target getter is not match this namespace
          if (type.slice(0, splitPos) !== namespace) { return }
      
          // extract local getter type
          var localType = type.slice(splitPos);
      
          // Add a port to the getters proxy.
          // Define as getter property because
          // we do not want to evaluate the getters in this time.
          Object.defineProperty(gettersProxy, localType, {
            get: function () { return store.getters[type]; },
            enumerable: true
          });
        });
      
        return gettersProxy
      }
  • registerGetter

    一樣道理其中,將全部模塊下的getters平鋪到_wrappedGetters, 當獲取不一樣模塊下的getters的時候會內置local.getters, local.state, store.getters, store.state

    • store.getters

      爲什麼訪問getter屬性相似於vue中的computed,緣由就在於將全部getter設置進了vm,而且在訪問的時候對於store.getter對象內部的每一個方法名爲key的函數設置了訪問器屬性,當外部進行調用的時候,返回計算屬性計算到的結果。

    • store.state

      prototypeAccessors$1 = { state: { configurable: true } }
      prototypeAccessors$1.state.get = function () {
        return this._vm._data.$$state
      };
      Object.defineProperties( Store.prototype, prototypeAccessors$1 );

這樣模塊內的基本要素mutation, actions, getters, state等所有註冊完了。

MapGetters, MapState, MapActions, MapGetters

這三個方法是爲了在vue中更加方便容易地置入以及使用,說白了就是經過命名空間組合的類型分別去_mutations, _actions, store.getters的對象中取對應的value, 因此第一個參數都爲命名空間名(若是有命名空間),第二個參數能夠是數組的形式也能夠是對象的形式,不管哪一種形式,最後都會進行標準化例如

mapGetters('user', [
    'GET_USER'                 =>       [{key: 'GET_USER', val: 'GET_USER'}]
])
mapGetters('user', {
    getUser: 'GET_USER'        =>       [{key: 'getUser', val: 'GET_USER'}]
})
mapMutations('user', [
    'SET_USER'                 =>       [{key: 'SET_USER', val: 'SET_USER'}]
])
mapMutations('user', {
    setUser: 'SET_USER'        =>       [{key: 'setUser', val: 'SET_USER'}]
})
mapActions('user', [
    'FETCH_USER'               =>       [{key: 'FETCH_USER', val: 'FETCH_USER'}]
])
mapActions('user', {
    fetchUser: 'FETCH_USER'    =>       [{key: 'fetchUser', val: 'FETCH_USER'}]
})

經過命名空間獲取對應的module, 這樣就可以獲取到該模塊的上下文context

固然還有另外一種寫法, 當其valfunction的時候, 會內置commit, dispatch參數

mapMutations('user', {
    setOtherUser: (commit) => {
        
    }
})
mapActions('user', {
    fetchOtherUser: (dispatch) => {
        
    }
})

最後mutation會進行commit.apply(this.$store, [val].concat(args)) action會進行dispatch.apply(this.$store, [val].concat(args))
state返回state[val],
getter直接返回store.getters[val],其中val爲其actions,mutations, getters方法名。

dispatch, commit

上面進行dispatch(_type, _payload)以及commit(_type, _payload, _options),其實處理了2件事情:

  • 處理傳參

    通常傳參都是:

    commit({
        type: 'SET_USER',
        payload: 1
    }),

    但也能夠

    commit('SET_USER', 1)

    他內部進行對參數的從新組合,若是是對象則type=obj.type; payload=obj, 若是有optionsoptions=payload

  • 找到對應方法進行調用

    經過key找到對應的方法數組,若是是commit,則遍歷數組依次執行數組內的方法,若是是dispatch,則將數組內的全部進行Promise.all, 返回Promise對象

接下來就能夠快樂地使用vuex了,進行dispatch和commit, 以及mapGetters等一系列操做了。

不得不說vuex內部的實現感受滿滿的基礎,對於平時知識點的複習以及理解完源碼對於平時項目的使用仍是頗有幫助的,最後有些不正確的地方但願能獲得大佬們的指正。

相關文章
相關標籤/搜索