博客原文html
Vuex 是一個專爲 Vue.js 應用程序開發的狀態管理模式。
這種集中管理應用狀態的模式相比父子組件通訊來講,使數據的通訊更方便,狀態的更改也更加直觀。前端
確定有很多同窗在寫 Vue 時使用過 new Vue()
建立 bus 進行數據通訊。vue
import Vue from 'vue'; const bus = new Vue(); export default { install(Vue) { Object.defineProperty(Vue.prototype, '$bus', { get () { return bus } }); } };
組件中使用 this.$bus.$on
this.$bus.$emit
監聽和觸發 bus 事件進行通訊。
bus 的通訊是不依賴組件的父子關係的,所以實際上能夠理解爲最簡單的一種狀態管理模式。
經過 new Vue()
能夠註冊響應式的數據,
下面基於此對 bus 進行改造,實現一個最基本的狀態管理:git
// /src/vuex/bus.js let Vue // 導出一個 Store 類,一個 install 方法 class Store { constructor (options) { // 將 options.state 註冊爲響應式數據 this._bus = new Vue({ data: { state: options.state } }) } // 定義 state 屬性 get state() { return this._bus._data.state; } } function install (_Vue) { Vue = _Vue // 全局混入 beforeCreate 鉤子 Vue.mixin({ beforeCreate () { // 存在 $options.store 則爲根組件 if (this.$options.store) { // $options.store 就是建立根組件時傳入的 store 實例,直接掛在 vue 原型對象上 Vue.prototype.$store = this.$options.store } } }) } export default { Store, install }
建立並導出 store 實例:github
// /src/store.js import Vue from 'vue' import Vuex from './vuex/bus' Vue.use(Vuex) // 調用 Vuex.install 方法 export default new Vuex.Store({ state: { count: 0 } })
建立根組件並傳入 store 實例:vuex
// /src/main.js import Vue from 'vue' import App from './App.vue' import store from './store' new Vue({ store, render: h => h(App) }).$mount('#app')
組件中使用示例:數組
<!-- /src/App.vue --> <template> <div id="app"> {{ count }} <button @click="changeCount">+1</button> </div> </template> <script> export default { name: 'app', computed: { count() { return this.$store.state.count; } }, methods: { changeCount() { this.$store.state.count++ } } } </script>
前一節經過 new Vue()
定義一個響應式屬性並經過 minxin 爲全部組件混入 beforeCreate 生命週期鉤子函數的方法爲每一個組件內添加 $store
屬性指向根組件的 store 實例的方式,實現了最基本的狀態管理。
繼續這個思路,下面從零一步步實現一個最基本的 Vuex。app
如下代碼的 git 地址: simple-vuex
let Vue; class Store {} function install() {} export default { Store, install }
// 執行 Vue.use(Vuex) 時調用 並傳入 Vue 類 // 做用是爲全部 vue 組件內部添加 `$store` 屬性 function install(_Vue) { // 避免重複安裝 if (Vue) { if (process.env.NODE_ENV !== 'production') { console.error('[vuex] already installed. Vue.use(Vuex) should be called only once.'); } return } Vue = _Vue; // 暫存 Vue 用於其餘地方有用到 Vue 上的方法 Vue.mixin({ // 全局全部組件混入 beforeCreate 鉤子,給每一個組件中添加 $store 屬性指向 store 實例 beforeCreate: function vuexInit() { const options = this.$options; if (options.store) { // 接收參數有=中有 store 屬性則爲根組件 this.$store = options.store; } else if (options.parent && options.parent.$store) { // 非根組件經過 parent 父組件獲取 this.$store = options.parent.$store; } } }) }
// 執行 new Vuex.Store({}) 時調用 class Store { constructor(options = {}) { // 初始化 getters mutations actions this.getters = {}; this._mutations = {}; this._actions = {}; // 給每一個 module 註冊 _children 屬性指向子 module // 用於後面 installModule 中根據 _children 屬性查找子 module 進行遞歸處理 this._modules = new ModuleCollection(options) const { dispatch, commit } = this; // 固定 commit dispatch 的 this 指向 Store 實例 this.commit = (type, payload) => { return commit.call(this, type, payload); } this.dispatch = (type, payload) => { return dispatch.call(this, type, payload); } // 經過 new Vue 定義響應式 state const state = options.state; this._vm = new Vue({ data: { state: state } }); // 註冊 getters mutations actions // 並根據 _children 屬性對子 module 遞歸執行 installModule installModule(this, state, [], this._modules.root); } // 定義 state commit dispatch get state() { return this._vm._data.state; } set state(v){ throw new Error('[Vuex] vuex root state is read only.') } commit(type, payload) { return this._mutations[type].forEach(handler => handler(payload)); } dispatch(type, payload) { return this._actions[type].forEach(handler => handler(payload)); } }
Store 類的構造函數中初始化 _modules 時是經過調用 ModuleCollection 這個類,內部從根模塊開始遞歸遍歷 modules 屬性,初始化模塊的 _children 屬性指向子模塊。frontend
class ModuleCollection { constructor(rawRootModule) { this.register([], rawRootModule) } // 遞歸註冊,path 是記錄 module 的數組 初始爲 [] register(path, rawModule) { const newModule = { _children: {}, _rawModule: rawModule, state: rawModule.state } if (path.length === 0) { this.root = newModule; } else { // 非最外層路由經過 reduce 從 this.root 開始遍歷找到父級路由 const parent = path.slice(0, -1).reduce((module, key) => { return module._children[key]; }, this.root); // 給父級路由添加 _children 屬性指向該路由 parent._children[path[path.length - 1]] = newModule; // 父級路由 state 中也添加該路由的 state Vue.set(parent.state, path[path.length - 1], newModule.state); } // 若是當前 module 還有 module 屬性則遍歷該屬性並拼接 path 進行遞歸 if (rawModule.modules) { forEachValue(rawModule.modules, (rawChildModule, key) => { this.register(path.concat(key), rawChildModule); }) } } }
Store 類的構造函數中調用 installModule ,經過 _modules 的 _children 屬性遍歷到每一個模塊並註冊 getters mutations actionsasync
function installModule(store, rootState, path, module) { if (path.length > 0) { const parentState = rootState; const moduleName = path[path.length - 1]; // 全部子模塊都將 state 添加到根模塊的 state 上 Vue.set(parentState, moduleName, module.state) } const context = { dispatch: store.dispatch, commit: store.commit, } // 註冊 getters mutations actions const local = Object.defineProperties(context, { getters: { get: () => store.getters }, state: { get: () => { let state = store.state; return path.length ? path.reduce((state, key) => state[key], state) : state } } }) if (module._rawModule.actions) { forEachValue(module._rawModule.actions, (actionFn, actionName) => { registerAction(store, actionName, actionFn, local); }); } if (module._rawModule.getters) { forEachValue(module._rawModule.getters, (getterFn, getterName) => { registerGetter(store, getterName, getterFn, local); }); } if (module._rawModule.mutations) { forEachValue(module._rawModule.mutations, (mutationFn, mutationName) => { registerMutation(store, mutationName, mutationFn, local) }); } // 根據 _children 拼接 path 並遞歸遍歷 forEachValue(module._children, (child, key) => { installModule(store, rootState, path.concat(key), child) }) }
installModule 中用來註冊 getters mutations actions 的函數:
// 給 store 實例的 _mutations 屬性填充 function registerMutation(store, mutationName, mutationFn, local) { const entry = store._mutations[mutationName] || (store._mutations[mutationName] = []); entry.push((payload) => { mutationFn.call(store, local.state, payload); }); } // 給 store 實例的 _actions 屬性填充 function registerAction(store, actionName, actionFn, local) { const entry = store._actions[actionName] || (store._actions[actionName] = []) entry.push((payload) => { return actionFn.call(store, { commit: local.commit, state: local.state, }, payload) }); } // 給 store 實例的 getters 屬性填充 function registerGetter(store, getterName, getterFn, local) { Object.defineProperty(store.getters, getterName, { get: () => { return getterFn( local.state, local.getters, store.state ) } }) } // 將對象中的每個值放入到傳入的函數中做爲參數執行 function forEachValue(obj, fn) { Object.keys(obj).forEach(key => fn(obj[key], key)); }
還有 modules、plugins 等功能尚未實現,並且 getters 的並無使用 Vue 的 computed 而只是簡單的以函數的形式實現,可是已經基本完成了 Vuex 的主要功能,下面是一個使用示例:
// /src/store.js import Vue from 'vue' import Vuex from './vuex' Vue.use(Vuex) export default new Vuex.Store({ state: { count: 0 }, mutations: { changeCount(state, payload) { console.log('changeCount', payload) state.count += payload; } }, actions: { asyncChangeCount(ctx, payload) { console.log('asyncChangeCount', payload) setTimeout(() => { ctx.commit('changeCount', payload); }, 500); } } })
<!-- /src/App.vue --> <template> <div id="app"> {{ count }} <button @click="changeCount">+1</button> <button @click="asyncChangeCount">async +1</button> </div> </template> <script> export default { name: 'app', computed: { count() { return this.$store.state.count; } }, methods: { changeCount() { this.$store.commit('changeCount', 1); }, asyncChangeCount() { this.$store.dispatch('asyncChangeCount', 1); } }, mounted() { console.log(this.$store) } } </script>
閱讀源碼的過程當中寫了一些方便理解的註釋,但願給你們閱讀源碼帶來幫助,github: vuex 源碼