轉載請註明出處 https://segmentfault.com/a/11...javascript
vuex2.0 和 vuex1.x 相比,API改變的仍是不少的,但基本思想沒什麼改變。vuex2.0 的源碼挺短,四五百行的樣子,兩三天就能讀完。我是國慶期間斷斷續續看完的,寫一下本身的理解。這裏使用的vuex版本是 2.0.0-rc6。在看這篇文章以前,建議先看一遍官方的vuex2.0 文檔,瞭解基本概念,否則以後的內容理解起來會很費勁。html
要想使用 vuex 有幾種方式, 這裏不細講。vue
CDNjava
<script src='path/vue.js'><script> <!-- 必須先引入 vue --> <script src='path/vuex.js'></script> <!-- 平時學習時建議使用完整版 -->
ES6語法 + webpackreact
import Vuex from 'vuex' var store = new Vuex.Store({}) Vuex.mapState({})
或者webpack
import { Store, mapState } from 'vuex' var store = new Store({}) mapState({})
vuex 只暴露出了6個方法,分別是git
var index = { Store: Store, install: install, mapState: mapState, mapMutations: mapMutations, mapGetters: mapGetters, mapActions: mapActions } return index;
其中 install
方法是配合 Vue.use
方法使用的,用於在 Vue 中註冊 Vuex ,和數據流關係不大。其餘的幾種方法就是咱們經常使用的。github
先看看 Store 方法,學習 vuex 最早接觸到的就是 new Store({})
了。那麼就先看看這個 Store 構造函數。web
var Store = function Store (options) { var this$1 = this; // 指向返回的store實例 if ( options === void 0 ) options = {}; // 使用構造函數以前,必須保證vuex已註冊,使用Vue.use(Vuex)註冊vuex assert(Vue, "must call Vue.use(Vuex) before creating a store instance.") // 須要使用的瀏覽器支持Promise assert(typeof Promise !== 'undefined', "vuex requires a Promise polyfill in this browser.") var state = options.state; if ( state === void 0 ) state = {}; var plugins = options.plugins; if ( plugins === void 0 ) plugins = []; var strict = options.strict; if ( strict === void 0 ) strict = false; // store internal state // store的內部狀態(屬性) this._options = options this._committing = false this._actions = Object.create(null) // 保存actions this._mutations = Object.create(null) // 保存mutations this._wrappedGetters = Object.create(null) // 保存包裝後的getters this._runtimeModules = Object.create(null) this._subscribers = [] this._watcherVM = new Vue() // bind commit and dispatch to self var store = this var ref = this; var dispatch = ref.dispatch; // 引用的是Store.prototype.dispatch var commit = ref.commit; // 引用的是Store.prototype.commit this.dispatch = function boundDispatch (type, payload) { // 綁定上下文對象 return dispatch.call(store, type, payload) } this.commit = function boundCommit (type, payload, options) { return commit.call(store, type, payload, options) } // strict mode this.strict = strict // 是否開啓嚴格模式 // init root module. // this also recursively registers all sub-modules // and collects all module getters inside this._wrappedGetters // 初始化 root module // 同時也會遞歸初始化全部子module // 而且收集全部的getters至this._wrappedGetters installModule(this, state, [], options) // initialize the store vm, which is responsible for the reactivity // (also registers _wrappedGetters as computed properties) // 重置vm實例狀態 // 同時在這裏把getters轉化爲computed(計算屬性) resetStoreVM(this, state) // apply plugins plugins.concat(devtoolPlugin).forEach(function (plugin) { return plugin(this$1); }) };
一開始會有兩個判斷條件,判斷 vuex 是否已經註冊,和當前瀏覽器是否支持 Promise
, assert
方法也挺簡單,若是傳入的第一個參數爲假值,則拋出一個錯誤。vuex
function assert (condition, msg) { if (!condition) { throw new Error(("[vuex] " + msg)) } }
接着往下看,接着會定義 state
, plugins
,strict
三個變量,分別是你傳入的 options 對應的選項。以後就是定義返回的 store 實例的一些內部狀態。先不要管它們具體是什麼,這個以後會慢慢講,這裏先看看 Store 構造函數都作了些什麼。再以後就是綁定 dispatch
和 commit
方法到 store
實例上。接下來就是整個 vuex 的核心方法 installModule
了,以後重置 vm
實例的狀態。
簡單點說,當你使用 Store 構造函數,它實際上作了這麼幾件事,首先定義給 store
實例定義一些內部屬性,以後就是綁定 dispatch
和 commit
的上下文對象永遠是 store
實例上,以後 installModule
根據傳入的 options
‘充實’ 內部狀態等等。
很重要的一個方法。貼上代碼
/* * store 就是 store 實例 * rootState 是使用構造函數options中定義的 state 對象 * path 路徑 * module 傳入的options */ function installModule (store, rootState, path, module, hot) { var isRoot = !path.length // 是不是root var state = module.state; var actions = module.actions; var mutations = module.mutations; var getters = module.getters; var modules = module.modules; // set state if (!isRoot && !hot) { // 找到要註冊的 path 的上一級 state var parentState = getNestedState(rootState, path.slice(0, -1)) // 定義 module 的 name var moduleName = path[path.length - 1] // store._withCommit方法以後會講 // 這裏先理解爲 執行傳入的函數 store._withCommit(function () { // 使用Vue.set方法 // parentState[moduleName] = state // 而且state變成響應式的 Vue.set(parentState, moduleName, state || {}) }) } // 以後設置 mutations, actions, getters, modules if (mutations) { Object.keys(mutations).forEach(function (key) { registerMutation(store, key, mutations[key], path) }) } if (actions) { Object.keys(actions).forEach(function (key) { registerAction(store, key, actions[key], path) }) } if (getters) { wrapGetters(store, getters, path) } if (modules) { Object.keys(modules).forEach(function (key) { installModule(store, rootState, path.concat(key), modules[key], hot) }) } }
這裏有個很重要的概念要理解,什麼是 path. vuex 的一個 store 實例能夠拆分紅不少個 module ,不一樣的 module 能夠理解成一個子代的 store 實例(事實上,module 確實和 store 具備同樣的結構),這是一種模塊化的概念。所以這裏的 path 能夠理解成是表示一種層級關係,當你有了一個 root state 以後,根據這個 root state 和 path 能夠找到 path 路徑對應的一個 local state, 每個 module 下的 mutations 和 actions 改變的都是這個local state,而不是 root state.
這裏在 Store 構造函數裏傳入的 path 路徑爲 []
,說明註冊的是一個root state. 再看看上一段代碼的最後
if (modules) { Object.keys(modules).forEach(function (key) { installModule(store, rootState, path.concat(key), modules[key], hot) }) }
若是傳入的options 中有 modules 選項,重複調用 installModule
, 這裏傳入的函數的 path 參數是 path.concat(key)
, 因此應該很好理解了。
簡單看一下 getNestedState
方法。
/* * state: Object, path: Array * 假設path = ['a', 'b', 'c'] * 函數返回結果是state[a][b][c] */ function getNestedState (state, path) { return path.length ? path.reduce(function (state, key) { return state[key]; }, state) : state }
reduce 方法接受一個函數,函數的參數分別是上一次計算後的值,和當前值,reduce 方法的第二個參數 state 是初始計算值。
若是 mutations
選項存在,那麼就註冊這個 mutations
,看一下它的實現。
/* * 註冊mutations,也就是給store._mutations添加屬性 * 這裏說一下handler * handler 是 mutations[key] * 也就是傳入 Store構造函數的 mutations */ function registerMutation (store, type, handler, path) { if ( path === void 0 ) path = []; // 在_mutations中找到對應type的mutation數組 // 若是是第一次建立,就初始化爲一個空數組 var entry = store._mutations[type] || (store._mutations[type] = []) // 推入一個對原始mutations[key]包裝過的函數 entry.push(function wrappedMutationHandler (payload) { // store.state表示root state, 先獲取path路徑下的local state // mutation應該是對path路徑下的state的修改 // 函數接受一個payload參數 // 初始的handler,接受一個state he payload 參數 handler(getNestedState(store.state, path), payload) }) }
邏輯很簡單,全部的 mutations 都通過處理後,保存在了 store._mutations 對象裏。 _mutations 的結構爲
_mutations: { type1: [wrappedFunction1, wrappedFuction2, ...], type2: [wrappedFunction1, wrappedFuction2, ...], ... }
function registerAction (store, type, handler, path) { if ( path === void 0 ) path = []; var entry = store._actions[type] || (store._actions[type] = []) var dispatch = store.dispatch; var commit = store.commit; entry.push(function wrappedActionHandler (payload, cb) { var res = handler({ dispatch: dispatch, commit: commit, getters: store.getters, state: getNestedState(store.state, path), rootState: store.state }, payload, cb) // 若是 res 不是 promise 對象 ,將其轉化爲promise對象 // 這是由於store.dispatch 方法裏的 Promise.all()方法。 if (!isPromise(res)) { res = Promise.resolve(res) } if (store._devtoolHook) { return res.catch(function (err) { store._devtoolHook.emit('vuex:error', err) throw err }) } else { return res } }) }
這裏一樣是'充實' store._actions 對象,每一種 action type 都對應一個數組,數組裏存放的包裝後的 handler 函數,因爲涉及到 promise,這裏我想在下一節結合 store 的 dispatch 實例方法一塊兒講。
/* * 包裝getters函數 * store增長一個 _wrappedGetters 屬性 * moduleGetters: 傳入的options.getters * modulePath: 傳入 installModule 函數的 path */ function wrapGetters (store, moduleGetters, modulePath) { Object.keys(moduleGetters).forEach(function (getterKey) { var rawGetter = moduleGetters[getterKey] // 原始的getter if (store._wrappedGetters[getterKey]) { // 若是已經存在,警告 console.error(("[vuex] duplicate getter key: " + getterKey)) return } store._wrappedGetters[getterKey] = function wrappedGetter (store) { // 接受三個參數 // local state // 全局的 getters // 全局的 state return rawGetter( getNestedState(store.state, modulePath), // local state store.getters, // getters store.state // root state ) } }) }
注意 這裏的全部 getters 都儲存在了全局的一個 _wrappedGetters 對象中,一樣屬性名是各個 getterKey ,屬性值一樣是一個函數,執行這個函數,將會返回原始 getter 的執行結果。
if (modules) { Object.keys(modules).forEach(function (key) { installModule(store, rootState, path.concat(key), modules[key], hot) }) }
若是 options 中有 modules 選項,那麼就遞歸調用 installModule
方法,注意這裏的 path 改變。
function resetStoreVM (store, state) { var oldVm = store._vm // 原來的_vm // bind store public getters store.getters = {} // 初始化 store 的 getters 屬性爲一個空數組。 var wrappedGetters = store._wrappedGetters var computed = {} Object.keys(wrappedGetters).forEach(function (key) { var fn = wrappedGetters[key] // use computed to leverage its lazy-caching mechanism // 將wrappedGetter中的屬性轉移到 computed 中 computed[key] = function () { return fn(store); } // store.getters[key] = store._vm[key] Object.defineProperty(store.getters, key, { get: function () { return store._vm[key]; } }) }) // use a Vue instance to store the state tree // suppress warnings just in case the user has added // some funky global mixins // 設爲 silent 模式 var silent = Vue.config.silent Vue.config.silent = true // 初始化一個 store._vm 實例 store._vm = new Vue({ data: { state: state }, computed: computed }) Vue.config.silent = silent // enable strict mode for new vm // 啓用嚴格模式 if (store.strict) { enableStrictMode(store) } if (oldVm) { // dispatch changes in all subscribed watchers // to force getter re-evaluation. store._withCommit(function () { oldVm.state = null }) // 執行destroy 方法,通知全部的watchers 改變,並從新計算getters的值。 Vue.nextTick(function () { return oldVm.$destroy(); }) } }
這個方法在 installModule
方法以後執行,來看看它都作了什麼。簡單點說,就是給 store 增長了一個 _vm 屬性,指向一個新的 vue 實例,傳入的選項包括一個 state 和 computed, computed 來自store 的 getters 屬性。同時給 store 增長了一個 getters 屬性,且 store.getters[key] = store._vm[key]
在講 mapState
以前,先說一下基礎方法 normalizeMap
/* * 若是map是一個數組 ['type1', 'type2', ...] * 轉化爲[ * { * key: type1, * val: type1 * }, * { * key: type2, * val: type2 * }, * ... * ] * 若是map是一個對象 {type1: fn1, type2: fn2, ...} * 轉化爲 [ * { * key: type1, * val: fn1 * }, * { * key: type2, * val: fn2 * }, * ... * ] */ function normalizeMap (map) { return Array.isArray(map) ? map.map(function (key) { return ({ key: key, val: key }); }) : Object.keys(map).map(function (key) { return ({ key: key, val: map[key] }); }) }
normalizeMap
函數接受一個對象或者數組,最後都轉化成一個數組形式,數組元素是包含 key 和 value 兩個屬性的對象。
再來看看 mapState
方法。
/* * states: Object | Array * 返回一個對象 * 對象的屬性名對應於傳入的 states 的屬性名或者數組元素 * 屬性值都是一個函數 * 執行這個函數的返回值根據 val 的不一樣而不一樣 */ function mapState (states) { var res = {} normalizeMap(states).forEach(function (ref) { var key = ref.key; var val = ref.val; res[key] = function mappedState () { return typeof val === 'function' // 若是是函數,返回函數執行後的結果 ? val.call(this, this.$store.state, this.$store.getters) : this.$store.state[val] // 若是不是函數,而是一個字符串,直接在state中讀取。 } }) return res }
mapState
函數執行的結果是返回一個對象,屬性名對應於傳入的 states 對象或者數組元素。屬性值是一個函數,執行這個函數將返回相應的 state .
/* * mutations: Array * 返回一個對象 * 屬性名爲 mutation 類型 * 屬性值爲一個函數 * 執行這個函數後將觸發指定的 mutation */ function mapMutations (mutations) { var res = {} normalizeMap(mutations).forEach(function (ref) { var key = ref.key; // mutation type var val = ref.val; // mutation type res[key] = function mappedMutation () { var args = [], len = arguments.length; while ( len-- ) args[ len ] = arguments[ len ]; // 一個數組緩存傳入的參數 // val做爲commit函數的第一個參數type, 剩下的參數依次是payload 和 options return this.$store.commit.apply(this.$store, [val].concat(args)) } }) return res }
注意這裏傳入的 mutations
只能是一個數組,數組元素的 mutation type
. 函數的做用的也很簡單,傳入一個 mutations
數組,返回一個對象,屬性名是 mutation
的類型,屬性值是一個函數,執行這個函數,將調用 commit
來觸發對應的 mutation
從而改變state。另外注意這裏的 this
指向的 store 的 _vm
。mapState
是在 Vue 實例中調用的。
function mapActions (actions) { var res = {} normalizeMap(actions).forEach(function (ref) { var key = ref.key; var val = ref.val; res[key] = function mappedAction () { var args = [], len = arguments.length; while ( len-- ) args[ len ] = arguments[ len ]; return this.$store.dispatch.apply(this.$store, [val].concat(args)) } }) return res }
mapActions
函數和 mapMutations
函數幾乎一模一樣。惟一的區別即便這裏應該使用 dispatch
方法來觸發 action
.
/* * getters: Array */ function mapGetters (getters) { var res = {} normalizeMap(getters).forEach(function (ref) { var key = ref.key; var val = ref.val; res[key] = function mappedGetter () { // 若是在getters中不存在,報錯 if (!(val in this.$store.getters)) { console.error(("[vuex] unknown getter: " + val)) } // 根據 val 在 getters 對象裏找對應的屬性值 return this.$store.getters[val] } }) return res }
這裏 getters
一樣接受一個數組,一樣返回一個對象。
以上講了四種 map***
方法,這四種方法能夠都返回一個對象,所以能夠 ES6 新特性 ...
解構符。如
{ ...mapState(options) }
關於 ...
解構符號, 舉個小例子就明白了
var obj1 = { a: 1, b: 2, c: 3 } var obj2 = { ...obj1, d: 4 } // obj2 = { a: 1, b: 2, c: 3, d: 4 } // 一樣能夠用於數組 var arr1 = ['a', 'b', 'c'] var arr2 = [...arr1, 'd'] // arr2 = ['a', 'b', 'c', 'd']
install
方法與 vuex 數據流關係不大,主要是用於在 Vue 中註冊 Vuex,這裏爲了保持篇幅的完整性,簡單介紹一下。
function install (_Vue) { if (Vue) { // 報錯,已經使用了 Vue.use(Vuex)方法註冊了 console.error( '[vuex] already installed. Vue.use(Vuex) should be called only once.' ) return } Vue = _Vue applyMixin(Vue) } // auto install in dist mode // 在瀏覽器環境寫,會自動調用 install 方法 if (typeof window !== 'undefined' && window.Vue) { install(window.Vue) }
沒什麼難度,那就再看一下 applyMixin 方法
function applyMixin (Vue) { var version = Number(Vue.version.split('.')[0]) // 檢查使用的 Vue 版本,初始化時的生命週期鉤子函數是 init 仍是 beforeCreate if (version >= 2) { var usesInit = Vue.config._lifecycleHooks.indexOf('init') > -1 Vue.mixin(usesInit ? { init: vuexInit } : { beforeCreate: vuexInit }) } else { // override init and inject vuex init procedure // for 1.x backwards compatibility. // 保存以前的 Vue.prototype._init var _init = Vue.prototype._init // 從新設置Vue.prototype._init Vue.prototype._init = function (options) { if ( options === void 0 ) options = {}; // 初始化時先初始化vuexInit // options.init: Array 表示一組要執行的鉤子函數 // options.init鉤子函數以前加上了 vueInit options.init = options.init ? [vuexInit].concat(options.init) : vuexInit _init.call(this, options) } } /* * Vuex init hook, injected into each instances init hooks list. */ function vuexInit () { var options = this.$options // store injection // 若是本身有store選項,用本身的 // 不然查找父組件的 if (options.store) { this.$store = options.store } else if (options.parent && options.parent.$store) { this.$store = options.parent.$store } } }
註釋寫的很清楚了,那麼再看看什麼有是 vuexInit
函數, vuexInit
函數是 vuex 的生命週期鉤子函數。函數傳遞了兩個信息,(1)子組件能夠有本身單獨的store,可是通常不這麼作 (2) 若是子組件沒有本身的 store ,就會查找父組件的。這也印證了 根組件的 store 會注入到全部的後代組件。
以上講解了 Vuex 暴露出的 6 種方法,也是 Vuex 裏的用的最多的幾種方法,以後還會解讀一下其餘一些方法,好比 store 的一些實例方法。
另外本文的 github 的地址爲: learnVuex2.0
轉載請註明原連接
全文完