vuex究竟是什麼? html
使用vue也有一段時間了,可是對vue的理解彷佛仍是停留在初始狀態,究其緣由,不得不說是本身沒有深刻進去,理解本質,致使開發效率低,永遠停留在表面, 更壞的結果就是refresh、restart。 vue
首先說說什麼是vue。 我對vue的理解是一個簡單、易上手的開源框架。能夠幫助咱們快速構建單頁面應用。因爲MVVM的特色,在使用起來數據和視圖的相互驅動使得頁面的效果很好,避免了多餘了http請求和繁瑣的事件函數綁定,使咱們在開發起來搞笑、暢快。 react
可是隻是使用vue,仍是存在問題的。好比在同一個頁面間的組件之間狀態的傳遞就會出現問題。 好比, 在一個購物頁面,咱們點擊按鈕增長物品的數量,而後在下方顯示總價,而且爲了組件的重用,按鈕所在的組件和總價所在的組件是不一樣的, 那麼若是但願二者進行傳遞數據,該怎麼處理呢? 咱們知道prop是用來從父組件向子組件進行傳遞的,因此不可行。在官網上也介紹了總線的使用,可是使用起來很是麻煩,因此vuex就排上了用場。 使用vuex,能夠幫助咱們在當前頁面順利的傳遞、分享狀態,可是若是是在不一樣的頁面,一旦刷新,頁面的state數據就會丟失了,從這裏,咱們須要知道的時vuex只是狀態管理的工具,而不是數據存儲的工具,若是但願使用數據存儲就必需要使用web Storage了。 官網所言以下:git
Vuex 是一個專爲 Vue.js 應用程序開發的狀態管理模式。它採用集中式存儲管理應用的全部組件的狀態,並以相應的規則保證狀態以一種可預測的方式發生變化。es6
也就是說vuex僅僅是一個管理狀態的工具,保證狀態能夠以一種能夠預測的方式發生變化,並無任何永久存儲的功能。github
下面,咱們來解析源碼web
進入vue的github,首先,咱們看到的就是這樣的一個結構。vuex
其中.github這種.開頭的文件通常都是配置型的文件,好比.github、.babelrc、.eslintrc等等,這些文件每每是不須要在正式環境中使用的。 因此在看源碼時是能夠忽略的。vue-cli
而build文件每每是用於建立一個項目時所須要的配置,好比vue-cli構建的項目中的各類構造(build)這個項目所須要配置的文件,並非vuex的核心文件。typescript
dist文件是最終生成的文件,好比咱們使用vue-cli時,在npm run build以後生成的能夠在實際項目中使用的文件就是dist文件,內容以下所示:
其中的vuex.js就是核心文件了,而 vuex.min.js 是壓縮後的文件,在生產中使用。esm.js文件多是在報錯的時候見得最多了,他會主動跟蹤咱們的文件的錯誤,而vm也是很是經常使用的,vm能夠理解爲追蹤,vue message等意思。 、
而logger之類的是日誌記錄。
接下來就是docs文件了,這個文件主要就是一些vuex的文檔,內容以下:
其中assets中鏈接了vuex的官方網站。en是英文文檔,fr是法國文檔,ja是日本文檔,kr是韓國文檔,old中記錄的時1.0的文檔,ru是俄文文檔,zh-cn就是中文文檔,example就是官網的一些例子的使用。src就是vuex的源碼文件夾,最後說。 test也不是重點,其中包含了e2e和jshint的代碼檢測工具。 types是typescript的寫法介紹。 最後的一些文件都是可有可無的了。
也就是說,讀一個庫的文件,最重要的是讀取其src文件。以下所示:
咱們先來對這個文件作一個總體介紹:
以前說了,閱讀一個項目就要從他的入口文件開始,由於這纔是最爲核心的地方。
import { Store, install } from './store' import { mapState, mapMutations, mapGetters, mapActions, createNamespacedHelpers } from './helpers' export default { Store, install, version: '__VERSION__', mapState, mapMutations, mapGetters, mapActions, createNamespacedHelpers }
index.js很是簡短,一共就12行代碼,可是經過這12行代碼咱們就能夠知道vuex須要的是什麼、提供的是什麼了。而最最重要的時vuex提供了什麼!
從export咱們能夠一目瞭然的看出vuex暴露了哪些api, 其中Store是最重要的vuex提供的狀態存儲類,咱們使用vuex就是先要建立這個Store類,好比下面的代碼:
Vue.use(Vuex) export default new Vuex.Store({ state: { totalPrice: 0, items: [], }, mutations: { // ... } })
這裏咱們先引入vuex,而後Vue.use(Vuex),表示使用Vuex插件,而use能夠引用就是由於咱們暴露了install接口,這樣在咱們在執行Vue.use(Vuex)的時候就自動執行了install方法將Vuex做爲引用。
而後導出了version即vuex的版本, 還有咱們常用的mapState輔助函數、mapMutations輔助函數、mapGetters輔助函數、mapActions輔助函數、createNamespacedHelpers建立命名空間幫助函數。
而createNamespaceHelpers主要是爲了建立不一樣模塊的命令空間,解決命名衝突的問題。
正是由於vuex暴露了這些接口,因此咱們在寫代碼時,纔會出現以下所示的使用方法:
import {mapState, mapMutations, mapActions} from 'vuex' export default { methods: { ...mapMutations([ 'UPDATE_CONTENT', "UPDATE_KINDS", "PUSH_TO_ITEMS", "UPDATE_CURINDEX" ]), ...mapActions([ 'updateKinds', 'updateContent', 'updateAllContent', 'updateMall', 'getCurContent', 'updateAllContentSync' ]),
OK!到這就介紹了index.js,能夠發現的時,這個入口文件的做用就是從不一樣的文件引入接口,而後再統一的從入口文件這暴露出去。就像我寫的vue-toast同樣,最後必定要暴露出module.exports = Toast; 這樣,在Vue.use(vue-toast)以後才能開始使用這個暴露的接口。
從這個入口文件能夠看出,他須要的時 store.js 和 helpers.js ,那麼剩下的文件呢? 顯示是被 store.js 和 helpers.js引入使用了。 因此沿着入口文件中import的文件,咱們緊接着來看一看 store.js 文件。
這個文件當屬vux中最爲重要的文件了。 閱讀源碼的好處在於更好的使用庫而且理解其中的使用方法來提高本身,因此,對於這篇400餘行的代碼,咱們採起精度的方式。下面開始 !
對於這種模塊化的文件,咱們最早要看的就是他須要的是什麼,提供的是什麼。 很容易能夠看出,store.js 的意義就在於道出了一個Store對象 和 一個install對象。以前咱們也提到過,是index.js中引入並導出的。
爲了導出 Store對象和install對象, store.js引入了下面的一些模塊:
import applyMixin from './mixin' import devtoolPlugin from './plugins/devtool' import ModuleCollection from './module/module-collection' import { forEachValue, isObject, isPromise, assert } from './util'
至於這些模塊具體是什麼做用,咱們先說一下後面在具體瞭解。 applyMixin的做用是在 vue 實例初始化時提供一個 $store 方法供vue調用。 deveollPlugin 的主要做用是利用vue的開發者工具來展現vuex中的數據狀態,方便開發者的調試。 ModuleCollection的做用是支持 vuex 經過分模塊的傳入(collection就是收集的意思,將全部的vuex模塊收集起來),能夠是咱們的開發更爲高效,由於狀態一多,分模塊才更容易管理, 這才稍早的版本中是不存在的。而forEachValu、isObject等就是一些通用的方法,在下面解讀的時候咱們具體來說。
let Vue // bind on install
定義局部 Vue 變量,用於判斷是否已經裝載和減小全局做用域查找。
環境判斷:
接下來就是使用es6的語法,聲明瞭一個store類:
export class Store { constructor (options = {}) { if (process.env.NODE_ENV !== 'production') { assert(Vue, `must call Vue.use(Vuex) before creating a store instance.`) assert(typeof Promise !== 'undefined', `vuex requires a Promise polyfill in this browser.`) assert(this instanceof Store, `Store must be called with the new operator.`) }
在開頭講了, assert是util.js中的,咱們看看assert的源碼:
export function assert (condition, msg) { if (!condition) throw new Error(`[vuex] ${msg}`) }
即給定一個條件,若是不知足,就拋出一個錯誤,這裏使用了es6的模板字符串方法。
因此這裏是在判斷處於開發環境下,而且Vue是存在的,不然就會報錯,提示沒有Vue.use(Vuex)。 接着判斷 Promise 是否可用,由於vuex是基於promise的,若是不可用,咱們須要使用polyfill的方式實現promise。 這行代碼的目的是爲了確保 Promsie 可使用的,由於 Vuex 的源碼是依賴 Promise 的。Promise 是 es6 提供新的 API,因爲如今的瀏覽器並非都支持 es6 語法的,因此一般咱們會用 babel 編譯咱們的代碼,若是想使用 Promise 這個 特性,咱們須要在 package.json 中添加對 babel-polyfill 的依賴並在代碼的入口加上 import 'babel-polyfill'
這段代碼。
接着就是 Store必須是用new操做符來建立的,因此這裏的this時Store類型。
數據初始化:
構造函數接下來的代碼是這樣的:
const { plugins = [], strict = false } = options let { state = {} } = options if (typeof state === 'function') { state = state() }
這裏利用了對象的解構賦值,即options是咱們經過Vue.use(vuex, {}) 中的後者可能傳遞的對象, 將options中的plugins賦值給當前的plugins, 將strict賦值給當前的strict,而 plugins = []和strict=false是默認值, 即若是options不存在時咱們默認使用的值。
接下來定義了store的一些內部屬性(咱們習慣用_來表示內部屬性):
this._committing = false this._actions = Object.create(null) this._mutations = Object.create(null) this._wrappedGetters = Object.create(null) this._modules = new ModuleCollection(options) this._modulesNamespaceMap = Object.create(null) this._subscribers = [] this._watcherVM = new Vue()
其中,_committing標誌一個提交狀態,做用是 state只能在 mutation 的回調函數中修改,而不能再其餘地方修改, false就是不能修改。 this.actions是一個對象,它定義了用戶的全部actions。 一樣對於_mutations、_wrappedGetters。 subscribers定義了全部檢測 mutations 變化的訂閱者。 而 _watcherVM是vue對象的一個實例,使用$watch來檢測變化的。
// bind commit and dispatch to self const store = this const { dispatch, commit } = this 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) }
這裏的this綁定到了store實例上,而後定義了store的兩個重要的方法 dispatch和commit。 接受的參數分別是 type ,即commit的類型以及dispatch的類型, 以及一個payload, 從源碼就能夠看出咱們進行commit和dipatch的時候必定要傳遞一個payload。 這是很重要的。最後說明在 store 上調用。由於都是store的方法。
// strict mode this.strict = strict
線下環境建議開啓嚴格模式,線上環境建議關閉嚴格模式。
// init root module.
// this also recursively registers all sub-modules // and collects all module getters inside this._wrappedGetters installModule(this, state, [], this._modules.root) // initialize the store vm, which is responsible for the reactivity // (also registers _wrappedGetters as computed properties) resetStoreVM(this, state) // apply plugins plugins.concat(devtoolPlugin).forEach(plugin => plugin(this))
這幾行代碼的做用是遞歸地註冊子模塊。 而他的實現原理是什麼呢? 下面看一看installModule的源碼:
function installModule (store, rootState, path, module, hot) { const isRoot = !path.length const namespace = store._modules.getNamespace(path) // register in namespace map if (module.namespaced) { store._modulesNamespaceMap[namespace] = module } // set state if (!isRoot && !hot) { const parentState = getNestedState(rootState, path.slice(0, -1)) const moduleName = path[path.length - 1] store._withCommit(() => { Vue.set(parentState, moduleName, module.state) }) } const local = module.context = makeLocalContext(store, namespace, path) module.forEachMutation((mutation, key) => { const namespacedType = namespace + key registerMutation(store, namespacedType, mutation, local) }) module.forEachAction((action, key) => { const namespacedType = namespace + key registerAction(store, namespacedType, action, local) }) module.forEachGetter((getter, key) => { const namespacedType = namespace + key registerGetter(store, namespacedType, getter, local) }) module.forEachChild((child, key) => { installModule(store, rootState, path.concat(key), child, hot) }) }
即先判斷是不是根目錄, 若是path.length爲0則爲根目錄,接着得到命名空間, 若是命名空間是存在的,咱們就將模塊註冊到map中。
這裏再判斷若是不是根目錄,而且不是hot,咱們就經過getNestedState獲得parentState以及moduleName,而且將module的state註冊到parentState的moduleName中。由此來實現state註冊。而getNestedState實現也很是簡單:
function getNestedState (state, path) { return path.length ? path.reduce((state, key) => state[key], state) : state }
即根據path的長度來決定返回的state。 而後就是環境的管理,
命名空間和根目錄條件判斷完畢後,接下來定義local變量和module.context的值,執行makeLocalContext方法,爲該module設置局部的 dispatch、commit方法以及getters和state(因爲namespace的存在須要作兼容處理)。
接下來咱們就能夠開始循環註冊 mutations 和 actions了。
這樣就可使用了。 最後的介紹是關於一些基本方法的介紹。
http://tech.meituan.com/vuex-code-analysis.html
https://github.com/DDFE/DDFE-blog/issues/8
https://github.com/vuejs/vuex/blob/dev/src/store.js