摘要:簡單點說,當你使用構造函數,它實際上做了這么幾件事,首先定義給實例定義一些內部屬性,之后就是綁定和的上下文對象永遠是實例上,之后根據傳入的充實內部狀態等等。函數執行的結果是返回一個對象,屬性名對應于傳入的對象或者數組元素。
轉載請注明出處 https://segmentfault.com/a/11...
vuex2.0 和 vuex1.x 相比,API改變的還是很多的,但基本思想沒什么改變。vuex2.0 的源碼挺短,四五百行的樣子,兩三天就能讀完。我是國慶期間斷斷續續看完的,寫一下自己的理解。這里使用的vuex版本是 2.0.0-rc6。在看這篇文章之前,建議先看一遍官方的vuex2.0 文檔,了解基本概念,不然之后的內容理解起來會很費勁。
引入 vuex 文件要想使用 vuex 有幾種方式, 這里不細講。
CDN
ES6語法 + webpack
import Vuex from "vuex" var store = new Vuex.Store({}) Vuex.mapState({})
或者
import { Store, mapState } from "vuex" var store = new Store({}) mapState({})Store構造函數
vuex 只暴露出了6個方法,分別是
var index = { Store: Store, install: install, mapState: mapState, mapMutations: mapMutations, mapGetters: mapGetters, mapActions: mapActions } return index;
其中 install 方法是配合 Vue.use 方法使用的,用于在 Vue 中注冊 Vuex ,和數據流關系不大。其他的幾種方法就是我們常用的。
先看看 Store 方法,學習 vuex 最先接觸到的就是 new Store({}) 了。那么就先看看這個 Store 構造函數。
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 方法也挺簡單,如果傳入的第一個參數為假值,則拋出一個錯誤。
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 ‘充實’ 內部狀態等等。
installModule很重要的一個方法。貼上代碼
/* * 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 是初始計算值。
registerMutation如果 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, ...], ... }registerAction
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 實例方法一起講。
wrapGetters/* * 包裝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 的執行結果。
install modulesif (modules) { Object.keys(modules).forEach(function (key) { installModule(store, rootState, path.concat(key), modules[key], hot) }) }
如果 options 中有 modules 選項,那么就遞歸調用 installModule 方法,注意這里的 path 改變。
resetStoreVMfunction 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在講 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 .
mapMutations/* * 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 實例中調用的。
mapActionsfunction 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.
mapGetters/* * 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
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
轉載請注明原鏈接
全文完
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/80628.html
摘要:至此它便作為一個唯一數據源而存在。改變中的狀態的唯一途徑就是顯式地提交。這樣使得可以方便地跟蹤每一個狀態的變化,從而能夠實現一些工具幫助更好地了解應用背后的基本思想,借鑒了和參考源碼解讀一架構入門教程狀態管理 準備工作 1.下載安裝運行 這里以2.0.0-rc.6為例官網github下載鏈接(對應版本):https://github.com/vuejs/vuex...點擊下載到本地 ...
摘要:第一篇文章我會結合和的部分源碼,來說明注入生命周期的過程。說到源碼,其實沒有想象的那么難。但是源碼的調用樹會復雜很多。應用的業務代碼逐漸復雜,事件事件總線等通信的方式的弊端就會愈發明顯。狀態管理是組件解耦的重要手段。前言 這篇文章是【前端詞典】系列文章的第 13 篇文章,接下的 9 篇我會圍繞著 Vue 展開,希望這 9 篇文章可以使大家加深對 Vue 的了解。當然這些文章的前提是默認你對 ...
摘要:無需使用服務器實時動態編譯,而是使用預渲染方式,在構建時簡單地生成針對特定路由的靜態文件。與可以部署在任何靜態文件服務器上的完全靜態單頁面應用程序不同,服務器渲染應用程序,需要處于運行環境。更多的服務器端負載。 目錄結構 -no-ssr-demo 未做ssr之前的項目代碼用于對比 -vuecli2ssr 將vuecli生成的項目轉為ssr -prerender-demo 使用prer...
摘要:個人看來,一個狀態管理的應用,無論是使用,還是,最困難的部分是在的設計。中,并沒有移除,而是改為用于觸發。也是一個對象,用于注冊,每個都是一個用于返回一部分的。接受一個數組或對象,根據相應的值將對應的綁定到組件上。 系列文章: Vue 2.0 升(cai)級(keng)之旅 Vuex — The core of Vue application (本文) 從單頁應用(SPA)到服務器...
摘要:學習,首先明白是什么是一個專為應用程序開發的狀態管理模式。它采用集中式存儲管理應用的所有組件的狀態,并以相應的規則保證狀態以一種可預測的方式發生變化。 學習vuex,首先明白vuex是什么?Vuex 是一個專為 Vue.js 應用程序開發的狀態管理模式。它采用集中式存儲管理應用的所有組件的狀態,并以相應的規則保證狀態以一種可預測的方式發生變化。 如果你在使用 vue.js , 那么我...
閱讀 1809·2019-08-30 13:54
閱讀 2725·2019-08-29 17:27
閱讀 1109·2019-08-29 17:23
閱讀 3350·2019-08-29 15:20
閱讀 1225·2019-08-29 11:28
閱讀 1566·2019-08-26 10:39
閱讀 1315·2019-08-26 10:29
閱讀 639·2019-08-26 10:13