突然之间看到 vue2.x 文档中的两个 apiVue.observable(object) 可以让一个对象可响应,并且返回的对象可以直接用于渲染函数和计算属性中,并且会在发生变更时触发相应的更新,也可以作为最小化的跨组件状态存储器

于是心血来潮决定尝试写一个 vue 的状态管理插件

关于上方提到的两个 api 可以在下方文档中找到,这里不多作介绍

使用脚手架创建一个 vue 项目

1
vue create app

因为 Vue.observable2.6.0 版本之后新增的,请确保 vuevue-template-compiler 的版本高于 2.6.0 且二者版本一致

插件源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
import Vue from 'vue';

// 属性私有化
const store = Symbol('store');

// 如果使用 lodash _.cloneDeep(value)
function deepClone(object){
if(object === null) return null;
if(typeof object !== 'object') return object;
if(object.constructor === Date) return new Date(object);

// 保持原型链不变
let temp = new object.constructor();

for(let key in object){
// 只要自身的属性 object.hasOwnProperty(key)
if(Object.prototype.hasOwnProperty.call(object, key)){
let value = object[key];
try{
// arguments.callee 为了解除和函数名的耦合 实际上 arguments.callee也指向函数 deepClone 严格模式下不允许使用arguments
temp[key] = typeof value === 'object' ? arguments.callee(value) : value;
}catch(error){
temp[key] = typeof value === 'object' ? deepClone(value) : value;
}

}
}

return temp;
}

class State{
[store] = {};
constructor(options){
this[store] = Vue.observable(options);
// this[store] = options;
}
// 只能通过dispatch来修改状态
dispatch(action){
if(action instanceof Array){
action.forEach(item => {
Vue.set(this[store], item.type, item.data);
})
}else{
let {type, data} = action;
Vue.set(this[store], type, data);
// this[store][type] = data;
}
}
getState(){
return deepClone(this[store]);
}
}

let state = {
install(Vue){
Vue.mixin({
beforeCreate(){
// 获取当前实例的自定义属性 store
let store = this.$options.store;

// 如果不存在,当前实例没有store属性 需要取到父组件的store
if(!store){
store = this.$parent.$store
}

this.$store = store;
},
// 下面的内容 混入以后就不能和组件的重复了
computed: {
$$state(){
return this.$store.getState();
}
},
methods: {
$$dispatch(action){
// action可以是数组或对象
// 对象形式 interface Action {type: string; data: unknown}
// 数组 Action[]
this.$store.dispatch(action)
}
}
})
},
State
}

export default state;

使用

此插件已发布至 npm 如需使用可参考以下链接
另外,请忽略版本的问题,之前的版本不可用,很多 bug

1
npm i vue-observable-plus@1.1.0 --save
1
<script src="https://cdn.jsdelivr.net/npm/vue-observable-plus@1.1.0/obs.min.js"></script>

举个栗子

下方的栗子中,会看到 $store.getState() 实际上全局混入之后,计算属性中的 $$state 就表示 this.$store.getState(),同时 $$dispatch$store.dispatch 效果相同,请忽略下方栗子中的 getState 这是因为分享的 codesanbox 和本地的 codesanbox 预览效果不一致,读者测试时完全可以使用 $$state 而不是 getState(),这里仅仅是为了预览效果,浏览器直接使用 $$state 不会报错

end

第一次写 vue 插件,如果有任何问题,欢迎读者留言指正~