介绍
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 — 杨夕