在閱讀Vuex源碼以前,由於Vuex的api和使用功能略微複雜,默認覺得實現起來至關複雜,望而生畏。然而經過深刻學習源碼,發現核心功能結合vue實現起來很是巧妙,也就核心幾行代碼,直呼內行。本文也就100左右行代碼就能快速手寫本身的Vuex代碼!html
Vuex 是⼀個專爲 Vue.js應⽤程序開發的狀態管理模式。它採⽤集中式存儲管理應⽤的全部組件的狀態,並以相應的規則保證狀態以⼀種可預測的⽅式發⽣變化。那麼Vuex和單純的全局對象有什麼不一樣呢?vue
Vuex 的狀態存儲是響應式的。當 Vue 組件從 store 中讀取狀態的時候,若 store 中的狀態發⽣變 化,那麼相應的組件也會相應地獲得⾼效更新。不能直接改變 store 中的狀態。改變 store 中的狀態的惟⼀途徑就是顯式地提交 (commit) mutation。這樣使得咱們能夠⽅便地跟蹤每⼀個狀態的變化,從⽽讓咱們可以實現⼀些⼯具幫助我 們更好地瞭解咱們的應⽤。git
經過以上兩點認知咱們來快速實現本身的Vuex!github
爲何在vue實例化的時候要傳入store
去實例化呢?那是爲了讓vue
全部的組件中能夠經過 this.$store
來獲取該對象,即 this.$store
指向 store
實例。vuex
// Store 待實現 const store = new Store({ state: { count: 0, num: 10 }) new Vue({ el: '#app', store: store // 此處的 store 爲 this.$options.store })
Vuex
提供了install
屬性,經過Vue.use(Vuex)
來註冊。api
const install = function (Vue) { Vue.mixin({ beforeCreate() { if (this.$options.store) { Vue.prototype.$store = this.$options.store } } }) }
Vue全局混⼊了⼀個 beforeCreated
鉤⼦函數, options.store
保存在全部組件的 this.$store
中,這個 options.store
就是咱們在實例化 Store
對象的實例。Store 對象的構造函數接收⼀個對象參數,它包含 actions
、 getters
、 state
、 mutations
等核⼼概念,接下來咱們一一實現。promise
其實 state 是 vue 實例中的 data ,經過 Store 內部建立Vue實例,將 state 存儲到 data 裏,而後改變 state 就是觸發了 data 數據的改變從而實現了視圖的更新。app
// 實例化 Store const store = new Store({ state: { count: 0, num: 10 } }) // Store 實現 class Store { constructor({state = {}}) { this.vm = new Vue({ data: {state} // state 添加到 data 中 }) } get state() { return this.vm.state // 將 state代理到 vue 實例中的 state } set state(v) { console.warn(`Use store.replaceState() to explicit replace store state.`) } }
由上可知,store.state.count
等價於 store.vm.state
。不管是獲取或者改變state裏面的數據都是間接的觸發了vue中data數據的變化,從而觸發視圖更新。異步
知道state
是vue
實例中的data
,那麼同理,getters 就是 vue中的計算屬性 computed。ide
// 實例化 Store const store = new Store({ state: { count: 0, num: 10 }, getters: { total: state => { return state.num + state.count } }, }) // Store 實現 class Store { constructor({state = {}, getters = {}}) { this.getters = getters // 建立模擬 computed 對象 const computed = {} Object.keys(getters).forEach(key => { const fn = getters[key] // 入參 state 和 getters computed[key] = () => fn(this.state, this.getters) // 代理 getters 到 vm 實例上 Object.defineProperty(this.getters, key, { get: () => this.vm[key] }) }) // 賦值到 vue 中的 computed 計算屬性中 this.vm = new Vue({ data: { state, }, computed, }) } get state() { return this.vm.state } set state(v) { console.warn(`Use store.replaceState() to explicit replace store state.`) } }
使用 Object.defineProperty
將getters上的全部屬性都代理到了vm實例上的computed計算屬性中,也就是 store.getters.count
等價於 store.vm.count
。
mutations等同於發佈訂閱模式,先在mutations中訂閱事件,而後再commit發佈事件。
// 實例化 Store const store = new Store({ state: { count: 0, num: 10 }, mutations: { INCREASE: (state, n) =>{ state.count += n }, DECREASE: (state, n) =>{ state.count -= n } } }) // Store 實現 class Store { constructor({state = {}, mutations = {}, strict = false}) { this.mutations = mutations // 嚴格摸索只能經過 commit 改變 state this.strict && this.enableStrictMode() } commit(key, payload) { // 獲取事件 const fn = this.mutations[key] // 開始 commit this.committing = true // 執行事件 並傳參 fn(this.state, payload) // 結束 commit 因此說明 commit 只能執行同步事件 this.committing = false } enableStrictMode () { // vm實例觀察 state 是否由 commit 觸發改變 this.vm.$watch('state', () => { !this.committing && console.warn(`Do not mutate vuex store state outside mutation handlers.`) }, { deep: true, sync: true }) } get state() { return this.vm.state } set state(v) { console.warn(`Use store.replaceState() to explicit replace store state.`) } }
store.commit
執行 mutations
中的事件,經過發佈訂閱實現起來並不難。 Vuex
中的嚴格模式,只能在commit
的時候改變state
數據,否則提示錯誤。
mutations
用於同步更新 state
,而 actions
則是提交 mutations
,並可進行異步操做,從而間接更新 state
。
// 實例化 Store const store = new Store({ actions: { getToal({dispatch, commit, state, getters}, n){ return new Promise(resolve => { setTimeout(() => { commit('DECREASE', n) resolve(getters.total) }, 1000) }) } } }) // Store 實現 class Store { constructor({actions = {}}) { this.actions = actions } dispatch(key, payload) { const fn = this.actions[key] const {state, getters, commit, dispatch} = this // 注意 this 指向 const result = fn({state, getters, commit: commit.bind(this), dispatch: dispatch.bind(this)}, payload) // 返回 promise return this.isPromise(result) ? result : Promise.resolve(result) } // 判斷是不是 promise isPromise (val) { return val && typeof val.then === 'function' } }
mutations
和 actions
的實現大同小異,actions
核心在於處理異步邏輯,並返回一個 promise
。
這邊把以上的代碼統一概括起來,能夠根據這份完整代碼來分析Vuex
邏輯。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <script src="https://cdn.staticfile.org/vue/2.2.2/vue.min.js"></script> </head> <body> <div id="app"> <child-a></child-a> <child-b></child-b> </div> <script> class Store { constructor({state = {}, getters = {}, mutations = {}, actions = {}, strict = false}) { this.strict = strict this.getters = getters this.mutations = mutations this.actions = actions this.committing = false this.init(state, getters) } get state() { return this.vm.state } set state(v) { console.warn(`Use store.replaceState() to explicit replace store state.`) } init (state, getters) { const computed = {} Object.keys(getters).forEach(key => { const fn = getters[key] computed[key] = () => fn(this.state, this.getters) Object.defineProperty(this.getters, key, { get: () => this.vm[key] }) }) this.vm = new Vue({ data: {state}, computed, }) this.strict && this.enableStrictMode() } commit(key, payload) { const fn = this.mutations[key] this.committing = true fn(this.state, payload) this.committing = false } dispatch(key, payload) { const fn = this.actions[key] const {state, getters, commit, dispatch} = this const res = fn({state, getters, commit: commit.bind(this), dispatch: dispatch.bind(this)}, payload) return this.isPromise(res) ? res : Promise.resolve(res) } isPromise (val) { return val && typeof val.then === 'function' } enableStrictMode () { this.vm.$watch('state', () => { !this.committing && console.warn(`Do not mutate vuex store state outside mutation handlers.`) }, { deep: true, sync: true }) } } const install = function () { Vue.mixin({ beforeCreate() { if (this.$options.store) { Vue.prototype.$store = this.$options.store } } }) } // 子組件 a const childA = { template: '<div><button @click="handleClick">click me</button> <button @click="handleIncrease">increase num</button> <button @click="handleDecrease">decrease num</button></div>', methods: { handleClick() { this.$store.state.count += 1 }, handleIncrease() { this.$store.commit('INCREASE', 5) }, handleDecrease() { this.$store.dispatch('getToal', 5).then(data => { console.log('total', data) }) } } } // 子組件 b const childB = { template: '<div><h1>count: {{ count }}</h1><h1>total: {{ total }}</h1></div>', mounted() { // 嚴格模式下修改state的值將警告 // this.$store.state.count = 1 }, computed: { count() { return this.$store.state.count }, total(){ return this.$store.getters.total } } } const store = new Store({ state: { count: 0, num: 10 }, getters: { total: state => { return state.num + state.count } }, mutations: { INCREASE: (state, n) =>{ state.count += n }, DECREASE: (state, n) =>{ state.count -= n } }, actions: { getToal({dispatch, commit, state, getters}, n){ return new Promise(resolve => { setTimeout(() => { commit('DECREASE', n) resolve(getters.total) }, 1000) }) } } }) Vue.use({install}) new Vue({ el: '#app', components: { 'child-a': childA, 'child-b': childB }, store: store }) </script> </body> </html>
經過上面的完整案例可知,Vuex
核心代碼也就100
行左右,可是他巧妙的結合了vue
的data
和computeds
屬性,化繁爲簡,實現了複雜的功能,因此說vuex
是不能脫離vue
而獨立運行的。
本文是結合官網源碼提取核心思想手寫本身的Vuex
,而官網的Vuex
,爲了不store
結構臃腫,還實現了modules
等功能,具體實現能夠查看Vuex官網源碼。