博客原文
介绍
Vuex 是一个专为 Vue.js 应用程序开发的 状态管理模式 。
这种集中管理应用状态的模式相比父子组件通信来说,使数据的通信更方便,状态的更改也更加直观。
Bus
肯定有不少同学在写 Vue 时使用过 new Vue()
创建 bus 进行数据通信。
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 进行改造,实现一个最基本的状态管理:
// /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 实例:
// /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 实例:
// /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>
从零实现一个 Vuex
前一节通过 new Vue()
定义一个响应式属性并通过 minxin 为所有组件混入 beforeCreate 生命周期钩子函数的方法为每个组件内添加 $store
属性指向根组件的 store 实例的方式,实现了最基本的状态管理。
继续这个思路,下面从零一步步实现一个最基本的 Vuex。
以下代码的 git 地址:simple-vuex
整体结构
let Vue;
class Store {}
function install() {}
export default {
Store,
install
}
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;
}
}
})
}
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));
}
}
ModuleCollection 类
Store 类的构造函数中初始化 _modules 时是通过调用 ModuleCollection 这个类,内部从根模块开始递归遍历 modules 属性,初始化模块的 _children 属性指向子模块。
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);
})
}
}
}
installModule
Store 类的构造函数中调用 installModule,通过 _modules 的 _children 属性遍历到每个模块并注册 getters mutations actions
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 源码
参考
- vuex 源码:如何实现一个简单的 vuex — cobish
- 用 150 行代码实现 Vuex 80% 的功能 — 殷荣桧
- Vuex 源码解析 — 染陌同学
- Vuex 源码深度解析 — yck
- Vuex 源码分析 — 网易考拉前端团队
- Vue 源码(三)—— Vuex — 杨夕