博客原文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 源码