Vuex原理剖析
一.Vuex基本使用及用法
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式 + 库。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
以下是一个表示“单向数据流”理念的简单示意:
这个状态自管理应用包含以下几个部分:
- state,驱动应用的数据源;
- view,以声明方式将 state 映射到视图;
- actions,响应在 view 上的用户输入导致的状态变化。
使用示例:
// store/index.js文件
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
age: 28
},
getters: {
getAge(state) {
return state.age + 10;
}
},
mutations: {
changeAge(state, payload) {
state.age += payload
}
},
actions: {
changeAge({
commit
}, payload) {
setTimeout(() => {
commit('changeAge', payload);
}, 1000);
}
}
})
// index.js
import Vue from "vue"
import App from "./App.vue"
import store from "./store"
var vm = new Vue({
el: '#root',
store,
render: h => h(App)
})
<template>
<div id="app">
<div>{{$store.state.age}}</div>
</div>
<button @click="$store.commit('changeAge',2)">changeAge</button>
</template>
(1)在 store/index.js 中,导入vuex后,做了两件事:Vue.use(Vuex)和new Vuex.Store(options)的options配置
(2)在index.js中,导入new Vuex.Store(options)生成的实例store,然后new Vue的时候作为选项参数传入。
(3)在app.vue中,也就是vm的子组件实例都挂载了一个$store属性(install方法),可以直接通过this.$store访问。
使用方式也可以参考官方文档: v3.vuex.vuejs.org/zh/guide/
这里可以将 state 类比为组件的状态
getters类比为组件的计算属性
mutations类比为组件中的方法(可以更改组件的状态)
actions用于进行异步操作将结果提交给mutation
<div id="app">
age: {{this.$store.state.age}}
<br />
age01: {{this.$store.getters.getAge}}
<br />
<!-- dispatch对应的action -->
<button @click="$store.dispatch('changeAge',3)">可异步add+3</button>
<!-- commit 对应的mutation -->
<button @click="$store.commit('changeAge',5)">同步增加5</button>
</div>
这个$store属性是通过根实例传入的
new Vue({
store,
render: h => h(App)
}).$mount('#app')
内部会将store属性挂载在每个实例上命名为$store,这样所有组件都可以操作同一个store属性
二.自己实现Vuex模块
Vue.use会调用vuex.install方法;new Vuex.store表示Vuex.Store是个类。
2.1 Store类和install方法
当我们在new Vue(options)的选项中传入store实例后,它每一个子组件上面都有一个$store属性。挂载全局$store就是在install方法完成,借助Vue.mixin()实现。
// src/vuex@/index.js
export let Vue
class Store {
constructor(options) {
}
}
const install = function(_Vue) {
Vue = _Vue;
Vue.mixin({ // 全局注册一个混入,影响注册之后所有创建的每个 Vue 实例。
beforeCreate() {
if(this.$options.store) {
this.$store = this.$options.store;
}else if(this.$options.parent &&this.$options.parent.$store) {
// //子组件$store属性指向父组件的$store属性, 这样每个组件的$store,并且都指向根实例的$store
this.$store =this.$options.parent.$store
}
}
})
}
export default {
Store,
install,
}
2.2 另外一种方式
或者以下方式,实现入口文件,默认导出Store类和install方法
import { Store, install } from './store';
export default {
Store,
install
}
export {
Store,
install
}
1.install方法
import applyMixin from './mixin'
let Vue;
export class Store {
constructor(options){}
}
export const install = (_Vue) =>{
Vue = _Vue;
applyMixin(Vue);
}
当我们使用插件时默认会执行install方法并传入Vue的构造函数
2.mixin方法
const applyMixin = (Vue) => {
Vue.mixin({
beforeCreate: vuexInit
})
}
function vuexInit() {
const options = this.$options;
if (options.store) {
// 给根实例增加$store属性
this.$store = options.store;
} else if (options.parent && options.parent.$store) {
// 给组件增加$store属性
this.$store = options.parent.$store;
}
}
export default applyMixin
将store实例定义在所有的组件实例上
3.实现state
export class Store {
constructor(options){
let state = options.state;
// vuex完全依赖vue的响应式原理
this._vm = new Vue({
data:{
$$state:state,
}
});
}
get state(){ //访问器
return this._vm._data.$$state
}
}
将用户传入的数据定义在vue的实例上 (这个就是vuex核心)产生一个单独的vue实例进行通信,这里要注意的是定义$开头的变量不会被代理到实例上
定义forEachValue函数
// 参数为一个obj对象 和回调函数, 内部遍历obj执行fn函数
export function forEachValue (obj, fn) {
Object.keys(obj).forEach(key => fn(key, obj[key]))
}
4.实现 getters
this.getters = {};
const computed = {}
forEachValue(options.getters, (key, value) => {
computed[key] = () => {
return value(this.state); // this.state为getters定义的属性的参数, value 为obj对象中的值,也就是具体定义的函数🌟
}
Object.defineProperty(this.getters,key,{
//将key(也就是getters上用户定义的属性 比如add)挂载到this._vm上
get:()=> this._vm[key]
})
});
this._vm = new Vue({
data: {
$$state: state,
},
computed // 利用计算属性实现缓存
});
forEachValue为自定义的一个方法, 第一个参数options.getters为用户定义的对象属性, 第二个参数为一个回调函数,
5.实现mutations
export class Store {
constructor(options) {
this.mutations = {};
forEachValue(options.mutations, (key, value) => {
this.mutations[key] = (payload) => value.call(this, this.state, payload)// value 为obj对象中的值,也就是具体定义的函数🌟
/*定义this.mutations[key] 具体的方法赋值 也就是用户使用的increment函数
mutations:{
increment(state, payload) {
state.count += payload.acc;
}
*/
});
}
commit = (type, payload) => { // 用户执行的commit函数
this.mutations[type](payload);
}
}
6.实现actions
export class Store {
constructor(options) {
this.actions = {};
forEachValue(options.actions, (key, value) => {
this.actions[key] = (payload) => value.call(this, this,payload); // 和mutations,具体不同看下面
});
}
dispatch = (type, payload) => {
this.actions[type](payload);
}
}
map辅助函数
使用方式
computed:{
store_count() {
return this.$store.state.count
},
...mapGetters([ // 可以传入数组
'doneTodos',
'doneTodosFalse'
]),
...mapState([
'count',
])
}
1.mapState辅助函数 实现
arrList: 用户传入的数组
const mapState = arrList => {
let obj = {};
for (let i = 0; i < arrList.length; i++) {
let stateName = arrList[i]; //每一项, eg: 'count',
obj[stateName] = function() { // 将obj[count]挂载到computed上,
return this.$store.state[stateName]; // 返回的是src/store/index.js的Store里面定义的内容
};
}
return obj;
};
2.mapGetters实现
// 同mapState辅助函数思路
const mapGetters = arrList => {
let obj = {};
for (let i = 0; i < arrList.length; i++) {
let getterName = arrList[i]
obj[getterName] = function() {
return this.$store.getters[getterName];
};
}
return obj;
};
3.mapMutations实现
// 同mapState辅助函数思路
const mapMutations = mutationList=>{
let obj = {};
for (let i = 0; i < mutationList.length; i++) {
let type = mutationList[i]
obj[type] = function(payload){
this.$store.commit(type,payload);
}
}
return obj
}
4.mapActions实现
// 同mapState辅助函数思路
const mapActions = actionList=>{
let obj = {};
for (let i = 0; i < actionList.length; i++) {
let type = actionList[i]
obj[type] = function(payload){
this.$store.dispatch(type,payload);
}
}
return obj
}
实现模块 Module 机制
1.格式化用户数据
import ModuleCollection from './module/module-collection'
this._modules = new ModuleCollection(options);
import { forEachValue } from '../util'
export default class ModuleCollection {
constructor(options) {
this.register([], options)
}
register(path, rootModule) {
let newModule = {
_raw: rootModule,
_children: {},
state: rootModule.state
};
if (path.length == 0) {
this.root = newModule;
} else {
let parent = path.slice(0,-1).reduce((memo,current)=>{
return memo._children[current];
},this.root);
parent._children[path[path.length-1]] = newModule;
}
if (rootModule.modules) {
forEachValue(rootModule.modules, (module, moduleName) => {
this.register(path.concat(moduleName), module);
})
}
}
}
2.抽离模块类
export default class Module{
constructor(rawModule){
this._children = {};
this._rawModule = rawModule;
this.state = rawModule.state
}
getChild(key){
return this._children[key]
}
addChild(key,module){
this._children[key] = module
}
}
后续我们对模块扩展会更加的方便
3.安装模块
this._actions = {};
this._mutations = {};
this._wrappedGetters = {}
// 安装模块
installModule(this, state, [], this._modules.root);
在模块类中提供遍历方法
export default class Module {
constructor(rawModule) {
this._children = {};
this._rawModule = rawModule;
this.state = rawModule.state
}
getChild(key) {
return this._children[key]
}
addChild(key, module) {
this._children[key] = module
}
forEachMutation(fn) {
if (this._rawModule.mutations) {
forEachValue(this._rawModule.mutations, fn)
}
}
forEachAction(fn) {
if (this._rawModule.actions) {
forEachValue(this._rawModule.actions, fn)
}
}
forEachGetter(fn) {
if (this._rawModule.getters) {
forEachValue(this._rawModule.getters, fn)
}
}
forEachChild(fn) {
forEachValue(this._children, fn);
}
}
对模块进行安装操作
function installModule(store, rootState, path, module) {
if (path.length > 0) {
let parent = path.slice(0, -1).reduce((memo, current) => {
return memo[current];
}, rootState);
Vue.set(parent, path[path.length - 1], module.state);
}
module.forEachMutation((mutation, key) => {
store._mutations[key] = (store._mutations[key] || []);
store._mutations[key].push((payload) => {
mutation.call(store, module.state, payload);
});
});
module.forEachAction((action, key) => {
store._actions[key] = (store._actions[key] || []);
store._actions[key].push(function(payload) {
action.call(store, this, payload);
});
});
module.forEachGetter((getter, key) => {
store._wrappedGetters[key] = function() {
return getter(module.state);
}
});
module.forEachChild((child, key) => {
installModule(store, rootState, path.concat(key), child)
})
}
对dispatch和 action方法进行重写
commit = (type, payload) => {
this._mutations[type].forEach(fn => fn.call(this, payload));
}
dispatch = (type, payload) => {
this._actions[type].forEach(fn => fn.call(this, payload));
}
4.定义状态和计算属性
resetStoreVM(this, state);
function resetStoreVM(store, state) {
const computed = {};
store.getters = {};
const wrappedGetters = store._wrappedGetters
forEachValue(wrappedGetters, (fn, key) => {
computed[key] = () => {
return fn(store.state);
}
Object.defineProperty(store.getters, key, {
get: () => store._vm[key]
})
});
store._vm = new Vue({
data: {
$$state: state,
},
computed
});
}
5.实现命名空间
import { forEachValue } from '../util';
import Module from './module';
export default class ModuleCollection {
getNamespace(path) {
let module = this.root
return path.reduce((namespace, key) => {
module = module.getChild(key);
console.log(module)
return namespace + (module.namespaced ? key + '/' : '')
}, '');
}
}
export default class Module {
get namespaced(){
return !!this._rawModule.namespaced;
}
}
在绑定属性是增加命名空间即可
function installModule(store, rootState, path, module) {
let namespace = store._modules.getNamespace(path);
if (path.length > 0) {
let parent = path.slice(0, -1).reduce((memo, current) => {
return memo[current];
}, rootState);
Vue.set(parent, path[path.length - 1], module.state);
}
module.forEachMutation((mutation, key) => {
store._mutations[namespace + key] = (store._mutations[namespace + key] || []);
store._mutations[namespace + key].push((payload) => {
mutation.call(store, module.state, payload);
});
});
module.forEachAction((action, key) => {
store._actions[namespace + key] = (store._actions[namespace + key] || []);
store._actions[namespace + key].push(function(payload) {
action.call(store, this, payload);
});
});
module.forEachGetter((getter, key) => {
store._wrappedGetters[namespace + key] = function() {
return getter(module.state);
}
});
module.forEachChild((child, key) => {
installModule(store, rootState, path.concat(key), child)
})
}
6.注册模块
registerModule(path,rawModule){
if(typeof path == 'string') path = [path];
this._modules.register(path, rawModule);
installModule(this, this.state, path, rawModule.rawModule);
// 重新设置state, 更新getters
resetStoreVM(this,this.state);
}
实现模块的注册,就是将当前模块注册到_modules中
function resetStoreVM(store, state) {
+ let oldVm = store._vm;
const computed = {};
store.getters = {};
const wrappedGetters = store._wrappedGetters
forEachValue(wrappedGetters, (fn, key) => {
computed[key] = () => {
return fn(store.state);
}
Object.defineProperty(store.getters, key, {
get: () => store._vm[key]
})
});
store._vm = new Vue({
data: {
$$state: state,
},
computed
});
if(oldVm){
+ Vue.nextTick(() => oldVm.$destroy())
}
}
销毁上次创建的实例
四.插件机制
1.使用方式
function persists(store) { // 每次去服务器上拉去最新的 session、local
let local = localStorage.getItem('VUEX:state');
if (local) {
store.replaceState(JSON.parse(local)); // 会用local替换掉所有的状态
}
store.subscribe((mutation, state) => {
// 这里需要做一个节流 throttle lodash
localStorage.setItem('VUEX:state', JSON.stringify(state));
});
}
plugins: [
persists
]
这里我们要实现subscribe、replaceState方法
// 执行插件
options.plugins.forEach(plugin => plugin(this));
subscribe(fn){
this._subscribers.push(fn);
}
replaceState(state){
this._vm._data.$$state = state;
}
2.获取最新状态
function getState(store, path) {
let local = path.reduce((newState, current) => {
return newState[current];
}, store.state);
return local
}
module.forEachMutation((mutation, key) => {
store._mutations[namespace + key] = (store._mutations[namespace + key] || []);
store._mutations[namespace + key].push((payload) => {
mutation.call(store, getState(store,path), payload);
});
});
调用mutation时传入最新状态
六.区分mutation和action
this._committing = false;
_withCommitting(fn) {
let committing = this._committing;
this._committing = true; // 在函数调用前 表示_committing为true
fn();
this._committing = committing;
}
if (store.strict) {
// 只要状态一变化会立即执行,在状态变化后同步执行
store._vm.$watch(() => store._vm._data.$$state, () => {
console.assert(store._committing, '在mutation之外更改了状态')
}, { deep: true, sync: true });
}
严格模式下增加同步watcher,监控状态变化
store._withCommitting(() => {
mutation.call(store, getState(store, path), payload); // 这里更改状态
})
只有通过mutation更改状态,断言才能通过
replaceState(newState) { // 用最新的状态替换掉
this._withCommitting(() => {
this._vm._data.$$state = newState;
})
}
store._withCommitting(() => {
Vue.set(parent, path[path.length - 1], module.state);
})
内部更改状态属于正常更新,所以也需要用_withCommitting进行包裹