你們都知道vuex
是vue
的一個狀態管理器,它採用集中式存儲管理應用的全部組件的狀態,並以相應的規則保證狀態以一種可預測的方式發生變化。先看看vuex
下面的工做流程圖html
經過官方文檔提供的流程圖咱們知道,vuex
的工做流程,vue
state
中渲染到頁面;dispatch
來觸發action
;action
經過調用commit
,來觸發mutation
;mutation
來更改數據,數據變動以後會觸發dep
對象的notify
,通知全部Watcher
對象去修改對應視圖(vue的雙向數據綁定原理)。理解vuex
的工做流程咱們就看看vuex
在vue
中是怎麼使用的。git
首先用vue-cli
建立一個項目工程,以下圖,選擇vuex
,而後就是一路的回車鍵github
安裝好以後,就有一個帶有vuex
的vue
項目了。vuex
進入目錄而後看到,src/store.js
,在裏面加了一個狀態{count: 100}
,以下vue-cli
import Vue from 'vue' import Vuex from 'vuex' // 引入vuex Vue.use(Vuex) // 使用插件 export default new Vuex.Store({ state: { count: 100 // 加一個狀態 }, getter: { }, mutations: { }, actions: { } })
最後在App.vue文件裏面使用上這個狀態,以下數組
<template> <div id="app"> 這裏是stort------->{{this.$store.state.count}} </div> </template> <script> export default { name: 'app' } </script> <style> </style>
項目跑起來就會看到頁面上看到,頁面上會有100了,以下圖app
到這裏咱們使用vuex
建立了一個store
,而且在咱們的App組件視圖中使用,可是咱們會有一些列的疑問。異步
store
是如何被使用到各個組件上的??state
的數據是雙向綁定的??this.$store.dispch
能夠觸發store
的actions
??this.$store.commit
能夠觸發store
的mutations
??帶着一堆問題,咱們來本身實現一個vuex
,來理解vuex
的工做原理。模塊化
在src
下新建一個vuex.js
文件,而後代碼以下
'use strict' let Vue = null class Store { constructor (options) { let { state, getters, actions, mutations } = options } } // Vue.use(Vuex) const install = _Vue => { // 避免vuex重複安裝 if (Vue === _Vue) return Vue = _Vue Vue.mixin({ // 經過mixins讓每一個組件實例化的時候都會執行下面的beforeCreate beforeCreate () { // 只有跟節點纔有store配置,因此這裏只走一次 if (this.$options && this.$options.store) { this.$store = this.$options.store } else if (this.$parent && this.$parent.$store) { // 子組件深度優先 父 --> 子---> 孫子 this.$store = this.$parent.$store } } }) } export default { install, Store }
而後修改store.js
中的引入vuex模塊改爲本身的vuex.js
import Vuex from './vuex' // 本身建立的vuex文件
在咱們的代碼中export default { install, Store }
導出了一個對象,分別是install
和Store
install
的做用是,當Vue.use(Vuex)
就會自動調用install
方法,在install
方法裏面,咱們用mixin
混入了一個beforeCreate
的生命週期的鉤子函數,使得當每一個組件實例化的時候都會調用這個函數。
在beforeCreate
中,第一次根組件經過store
屬性掛載$store
,後面子組件調用beforeCreate
掛載的$store
都會向上找到父級的$store
,這樣子經過層層向上尋找,讓每一個組件都掛上了一個$store
屬性,而這個屬性的值就是咱們的new Store({...})
的實例。以下圖
經過層層向上尋找,讓每一個組件都掛上了一個
$store
屬性
經過上面,咱們已經從每一個組件都經過this.$store
來訪問到咱們的store的實例,下面咱們就編寫state
數據,讓其變成雙向綁定的數據。下面咱們改寫store
類
class Store { constructor (options) { let { state, getters, actions, mutations } = options // 拿到傳進來的參數 this.getters = {} this.mutations = {} this.actions = {} // vuex的核心就是借用vue的實例,由於vuex的數據更改回更新視圖 this._vm = new Vue({ data: { state } }) } // 訪問state對象時候,就直接返回響應式的數據 get state() { // Object.defineProperty get 同理 return this._vm.state } }
傳進來的state
對象,經過new Vue({data: {state}})
的方式,讓數據變成響應式的。當訪問state
對象時候,就直接返回響應式的數據,這樣子在App.vue
中就能夠經過this.$store.state.count
拿到state
的數據啦,而且是響應式的呢。
上面咱們已經設置好state
爲響應式的數據,這裏咱們在store.js
裏面寫上mutations、actions、getters
,以下
import Vue from 'vue' import Vuex from './vuex' // 引入咱們的本身編寫的文件 Vue.use(Vuex) // 安裝store // 實例化store,參數數對象 export default new Vuex.Store({ state: { count : 1000 }, getters : { newCount (state) { return state.count + 100 } }, mutations: { change (state) { console.log(state.count) state.count += 10 } }, actions: { change ({commit}) { // 模擬異步 setTimeout(() => { commit('change') }, 1000) } } })
配置選項都寫好以後,就看到getters
對象裏面有個newCount
函數,mutations
和actions
對象裏面都有個change函數
,配置好store
以後咱們在App.vue
就能夠寫上,dispatch
和commit
,分別能夠觸發actions
和mutations
,代碼以下
<template> <div id="app"> 這裏是store的state------->{{this.$store.state.count}} <br/> 這裏是store的getter------->{{this.$store.getters.newCount}} <br/> <button @click="change">點擊觸發dispach--> actions</button> <button @click="change1">點擊觸發commit---> mutations</button> </div> </template> <script> export default { name: 'app', methods: { change () { this.$store.dispatch('change') // 觸發actions對應的change }, change1 () { this.$store.commit('change') // 觸發mutations對應的change } }, mounted () { console.log(this.$store) } } </script>
數據都配置好以後,咱們開始編寫store類,在此以前咱們先編寫一個循環對象工具函數。
const myforEach = (obj, callback) => Object.keys(obj).forEach(key => callback(key, obj[key])) // 做用: // 例如{a: '123'}, 把對象的key和value做爲參數 // 而後就是函數運行callback(a, '123')
工具函數都準備好了,以後,下面直接縣編寫getters
、mutations
和actions
的實現
class Store { constructor (options) { let { state = {}, getters = {}, actions = {}, mutations = {} } = options this.getters = {} this.mutations = {} this.actions = {} // vuex的核心就是借用vue的實例,由於vuex的數據更改回更新視圖 this._vm = new Vue({ data: { state } }) // 循環getters的對象 myforEach(getters, (getterName, getterFn) => { // 對this.getters對象進行包裝,和vue的computed是差很少的 // 例如 this.getters['newCount'] = fn(state) // 執行 this.getters['newCount']()就會返回計算的數據啦 Object.defineProperty(this.getters, getterName, { get: () => getterFn(state) }) }) // 這裏是mutations各個key和值都寫到,this.mutations對象上面 // 執行的時候就是例如:this.mutations['change']() myforEach(mutations, (mutationName, mutationsFn) => { // this.mutations.change = () => { change(state) } this.mutations[mutationName] = () => { mutationsFn.call(this, state) } }) // 原理同上 myforEach(actions, (actionName, actionFn) => { // this.mutations.change = () => { change(state) } this.actions[actionName] = () => { actionFn.call(this, this) } }) const {commit , dispatch} = this // 先存一份,避免this.commit會覆蓋原型上的this.commit // 解構 把this綁定好 // 經過結構的方式也要先調用這類,而後在下面在調用原型的對應函數 this.commit = type => { commit.call(this, type) } this.dispatch = type => { dispatch.call(this, type) } } get state() { // Object.defineProperty 同理 return this._vm.state } // commi調用 commit (type) { this.mutations[type]() } // dispatch調用 dispatch (type) { this.actions[type]() } }
經過上面的,咱們能夠看出,其實mutations
和actions
都是把傳入的參數,賦值到store
實例上的this.mutations
和this.actions
對象裏面。
當組件中this.$store.commit('change')
的時候 實際上是調用this.mutations.change(state)
,就達到了改變數據的效果,actions
同理。
getters是經過對Object.defineProperty(this.getters, getterName, {})
對this.getters進行包裝當組件中this.$store.getters.newCount
實際上是調用getters
對象裏面的newCount(state)
,而後返回計算結果。就能夠顯示到界面上了。
你們看看完成後的效果圖。
到這裏你們應該懂了vuex
的內部代碼的工做流程了,vuex
的一半核心應該在這裏了。爲何說一半,由於還有一個核心概念module
,也就是vuex
的數據的模塊化。
因爲使用單一狀態樹,應用的全部狀態會集中到一個比較大的對象。當應用變得很是複雜時,store 對象就有可能變得至關臃腫。
爲了解決以上問題,Vuex 容許咱們將 store 分割成模塊(module)。每一個模塊擁有本身的 state、mutation、action、getter、甚至是嵌套子模塊——從上至下進行一樣方式的分割
例以下面的store.js
// 實例化store,參數數對象 export default new Vuex.Store({ modules: { // 模塊a a: { state: { count: 4000 }, actions: { change ({state}) { state.count += 21 } }, modules: { // 模塊b b: { state: { count: 5000 } } } } }, state: { count : 1000 }, getters : { newCount (state) { return state.count + 100 } }, mutations: { change (state) { console.log(state.count) state.count += 10 } }, actions: { change ({commit}) { // 模擬異步 setTimeout(() => { commit('change') }, 1000) } } })
而後就能夠在界面上就能夠寫上this.$store.state.a.count(顯示a模塊count)
,this.$store.state.a.b.count(顯示a模塊下,b模塊的count)
,這裏還有一個要注意的,其實在組件中調用this.$store.dispatch('change')
會同時觸發,根的actions
和a模塊
的actions
裏面的change
函數。
下面咱們就直接去實現models
的代碼,也就是整個vuex
的實現代碼,
'use strict' let Vue = null const myforEach = (obj, callback) => Object.keys(obj).forEach(key => callback(key, obj[key])) class Store { constructor (options) { let state = options.state this.getters = {} this.mutations = {} this.actions = {} // vuex的核心就是借用vue的實例,由於vuex的數據更改回更新視圖 this._vm = new Vue({ data: { state } }) // 把模塊之間的關係進行整理, 本身根據用戶參數維護了一個對象 // root._children => a._children => b this.modules = new ModulesCollections(options) // 不管子模塊仍是 孫子模塊 ,全部的mutations 都是根上的 // 安裝模塊 installModules(this, state, [], this.modules.root) // 解構 把this綁定好 const {commit , dispatch} = this // 經過結構的方式也要先調用這類,而後在下面在調用原型的對應函數 this.commit = type => { commit.call(this, type) } this.dispatch = type => { dispatch.call(this, type) } } get state() { // Object.defineProperty 同理 return this._vm.state } commit (type) { // 由於是數組,因此要遍歷執行 this.mutations[type].forEach(fn => fn()) } dispatch (type) { // 由於是數組,因此要遍歷執行 this.actions[type].forEach(fn => fn()) } } class ModulesCollections { constructor (options) { // vuex [] // 註冊模塊 this.register([], options) } register (path, rawModule) { // path 是空數組, rawModule 就是個對象 let newModule = { _raw: rawModule, // 對象 _children: {}, // 把子模塊掛載到這裏 state: rawModule.state } if (path.length === 0) { // 第一次 this.root = newModule } else { // [a, b] ==> [a] let parent = path.slice(0, -1).reduce((root, current) => { return root._children[current] }, this.root) parent._children[path[path.length - 1]] = newModule } if (rawModule.modules) { // 遍歷註冊子模塊 myforEach(rawModule.modules, (childName, module) => { this.register(path.concat(childName), module) }) } } } // rootModule {_raw, _children, state } function installModules (store, rootState, path, rootModule) { // rootState.a = {count:200} // rootState.a.b = {count: 3000} if (path.length > 0) { // 根據path找到對應的父級模塊 // 例如 [a] --> path.slice(0, -1) --> [] 此時a模塊的父級模塊是跟模塊 // 例如 [a,b] --> path.slice(0, -1) --> [a] 此時b模塊的父級模塊是a模塊 let parent = path.slice(0, -1).reduce((root, current) => { return root[current] }, rootState) // 經過Vue.set設置數據雙向綁定 Vue.set(parent, path[path.length - 1], rootModule.state) } // 設置getter if (rootModule._raw.getters) { myforEach(rootModule._raw.getters, (getterName, getterFn) => { Object.defineProperty(store.getters, getterName, { get: () => { return getterFn(rootModule.state) } }) }) } // 在跟模塊設置actions if (rootModule._raw.actions) { myforEach(rootModule._raw.actions, (actionName, actionsFn) => { // 由於同是在根模塊設置,子模塊也有能相同的key // 全部把全部的都放到一個數組裏面 // 就變成了例如 [change, change] , 第一個是跟模塊的actions的change,第二個是a模塊的actions的change let entry = store.actions[actionName] || (store.actions[actionName] = []) entry.push(() => { const commit = store.commit const state = rootModule.state actionsFn.call(store, {state, commit}) }) }) } // 在跟模塊設置mutations, 同理上actions if (rootModule._raw.mutations) { myforEach(rootModule._raw.mutations, (mutationName, mutationFn) => { let entry = store.mutations[mutationName] || (store.mutations[mutationName] = []) entry.push(() => { mutationFn.call(store, rootModule.state) }) }) } // 遞歸遍歷子節點的設置 myforEach(rootModule._children, (childName, module) => { installModules(store, rootState, path.concat(childName), module) }) } const install = _Vue => { // 避免vuex重複安裝 if (Vue === _Vue) return Vue = _Vue Vue.mixin({ // 經過mixins讓每一個組件實例化的時候都會執行下面的beforeCreate beforeCreate () { // 只有跟節點纔有store配置 if (this.$options && this.$options.store) { this.$store = this.$options.store } else if (this.$parent && this.$parent.$store) { // 子組件深度優先 父 --> 子---> 孫子 this.$store = this.$parent.$store } } }) } export default { install, Store }
看到代碼以及註釋,主要流程就是根據遞歸的方式,處理數據,而後根據傳進來的配置,進行操做數據。
至此,咱們把vuex
的代碼實現了一遍,在咱們App.vue
的代碼裏添加
<template> <div id="app"> 這裏是store的state------->{{this.$store.state.count}} <br/> 這裏是store的getter------->{{this.$store.getters.newCount}} <br/> 這裏是store的state.a------->{{this.$store.state.a.count}} <br/> <button @click="change">點擊觸發dispach--> actions</button> <button @click="change1">點擊觸發commit---> mutations</button> </div> </template>
最後查看結果。
完結撒花~~~