手写响应式状态管理器
前端框架的快速发展,催生出一批又一批的轮子。在一些老旧的项目中,打造符合需求的轮子,提高开发效率,成为了应对产品经理和需求的难点和痛点。
这不又快到年底了,为了过好年,在年度考核中脱颖而出,特地和大家共享一个'轮子'(状态管理器)
。如果有不足的地方,评论区友好讨论😂。
实现思路:状态管理器的目标是提供一个仓库,对项目中所有的组件开放,各个文件可以在仓库中进行存取操作,如果某一个文件中对仓库中的某一个状态进行了监听,则可以实时获取该状态的最新值。大概的可以用下面的一张图来看:
实现步骤:
1、
class Observer {
constructor(obj) {
this._orData = null; // 被监听的仓库原始状态
this.state = obj.state; // 被监听的仓库状态
this.action = obj.action; // 被注册的仓库事件
this.watcher = obj.watcher; // 观察者
this.queueActions = new Set(); // 仓库事件队列
this.obData = Object.create(null); // 代理的仓库状态
this.reset = null; // 记录观察者
this.deepClone(obj.state); // 保存原始状态值,这里用到了深拷贝
this.cacheWatcher(obj.state); // 初始化 观察者
this.addObserve(); // 初始化仓库事件队列
this.observable(); // 激活仓库状态代理
this.protectValue(['_orData']); // 保护以下划线开头的属性,只读
}
observable() {
let that = this
this.obData = new Proxy(that.state, {
set: function (target, propKey, value) {
if (that.reset) {
that.reset.colletionObserve({
"target": target,
"key": propKey,
"val": value
});
}
Reflect.set(target, propKey, value);
return true
}
});
}
addObserve() {
this.queueActions.add(Reflect.ownKeys(this.action));
}
commit(key, val) { // 可以在提交事件的时候传入参数
let that = this;
this.queueActions.forEach(observer => {
for (let item of observer) {
if (item == key) {
that.action[key](that.obData, val);
}
}
});
}
cacheWatcher(obj) {
this.reset = new this.watcher(obj, this._orData); // 注册观察者watcher
}
deepClone(target) {
this._orData = lodash.cloneDeep(target); // 万能的lodash
}
protectValue(targetObj) {
const handler = {
get(target, key) {
return target[key];
},
set(target, key) {
console.error(`you can't change ${key} ,cause it's a private property`)
invariant(key, 'set');
return true;
}
};
function invariant(key, action) {
try {
throw new Error(`Invalid attempt to ${action} private "${key}" property`);
} catch (e) {
console.log(e)
}
}
for (let item of targetObj) { // 对带有_开头的对象实现了私有保护,只读
if (item[0] === '_') {
this[item] = new Proxy(this[item], handler)
}
}
}
}
let store = new Observer({
"state": state,
"action": action,
"watcher": watcher
});
2、实现watcher
class watcher {
constructor(obj, orData = null) {
this.obj = obj;
this.watchs = null;
this.temArr = [];
this.orData = orData; // 源数据已设为私有属性,不可更改
this.curPage = null; // 缓存当前页面的监听,便于手动清除缓存
this.changeData = Object.create(null); // 数据变化缓存最新值
}
colletionObserve(targetChange) {
/* 每次提交数据的变动可能并未有新值出现,比如一直提交21,则需要判断是否要触发对应的监听事件,因此这里需要对changeData进行代理操作,避免提交重复的值导致多次触发Observechange*/
const that = this;
this.changeData = new Proxy(that.changeData, {
set: function (target, propKey, value) {
if (Reflect.has(target, propKey)) { // 检查是否已有相同属性的值
if (Reflect.get(that.changeData, propKey) == value) { // 如果有相同属性出现,判断是否值相同
console.log('nothing to do ') // 如果相同则nothing
return false
} else { // 如果对应的值发生了改变,则通知下级事件
Reflect.set(target, propKey, value)
return true;
}
} else { // 没有相同属性的值则继续
Reflect.set(target, propKey, value)
return true;
}
} // state 数据发生变动通知观察者
});
if (Reflect.set(this.changeData, targetChange.key, targetChange.val)) {
this.Observechange(targetChange);
} // 总是缓存最新变动的值
}
Observechange(target) {
let orValue = null,
that = this,
newval = null,
curPage = this.curPage;
try {
that.temArr.forEach(item => {
if (Array.isArray(target)) { // 首次触发的时机,非commit事件
if (curPage.methods._state_ID == item.Id) { // 触发当前页面监听事件
for (let key of target) {
if (key == item.originKey) {
if (Reflect.has(that.orData, key)) { // 源数据中是否存在监听的Key值
if (Reflect.has(that.changeData, key)) { // 是否更新了值
newval = Reflect.get(that.changeData, key); // 获取最新的值
} else {
newval = Reflect.get(that.orData, key) // 返回原始数据
}
item.fn(Reflect.get(that.orData, key), newval) // 通知相关页面数据变动
} else {
throw new ReferenceError("Prop name \"" + key + "\" does not exist in state.");
}
}
}
}
} else {
if (target.key == item.originKey) {
if (Reflect.has(that.orData, target.key)) {
orValue = Reflect.get(that.orData, target.key); // 更新旧值
}
item.fn(orValue, target.val) // 通知相关页面数据变动
}
}
});
} catch (e) {
console.log(e)
}
}
typeCheck(obj) {
return typeof obj !== "object" ? typeof obj : Object.prototype.toString.call(obj).slice(8, -1).toLowerCase();
}
watch(params) {
let res = false;
if (this.curPage) {
res = this.differ(Reflect.ownKeys(params.methods), Reflect.ownKeys(this.curPage.methods))
}
if (!res) { // 避免重复添加
const timestamp = new Date().getTime(),
keys = Reflect.ownKeys(params.methods);
let fn = null,
temObj = {};
Reflect.set(params.methods, '_state_ID', timestamp); // 标识页面
this.curPage = params // 缓存
for (let key of keys) {
if (typeof (key) != 'symbol' && key.substring(0, 7) != '_state_') {
if (Reflect.has(this.orData, key)) {
fn = params.methods[key]
if (this.typeCheck(fn) == 'function') {
Reflect.set(temObj, 'originKey', key);
Reflect.set(temObj, 'Id', timestamp);
Reflect.set(temObj, 'fn', fn);
this.temArr.push(temObj);
temObj = {};
} else {
console.error(`You can't watch ${key},because it is not a function`)
}
} else {
console.error(`You can't watch ${key},because it is not be created in state`)
}
} else {
try {
throw new ReferenceError("You cannot set a key that begins with a '_state_',because it's a protect property");
} catch (error) {
console.log(error)
}
}
}
this.Observechange(keys); // 开始监听
}
}
differ(pageWatch, tem) { // 找出两个数组的交集
let temLen = tem.length - 1;
if (Reflect.has(tem, '_state_ID')) {
temLen = temLen - 1
}
let pageLen = pageWatch.length;
const set_intersection = (set1, set2) => {
if (set1.size > set2.size) {
return set_intersection(set2, set1);
}
const intersection = new Set();
for (const num of set1) {
if (set2.has(num)) {
intersection.add(num);
}
}
return [...intersection];
}
let intersection = function (nums1, nums2) {
const set1 = new Set(nums1);
const set2 = new Set(nums2);
return set_intersection(set1, set2);
};
const intersectionArr = intersection(pageWatch, tem)
if (intersectionArr.length != temLen || intersectionArr.length != pageLen) {
return false // 两次数组不一致 返回false
}
return true // 两次数组一致 返回true
}
clearWatchMemory() {
const pageWatch = this.curPage;
if (!pageWatch) { // 如果当前页面没有pageWatch 说明还未有监听方法在执行或者入参错误
try {
throw new ReferenceError("You cannot run clearWatchMemory method,because the Memory is empty");
} catch (error) {
console.log(error)
}
return
} else {
let pageData = pageWatch.methods;
let pageId = Reflect.get(pageData, '_state_ID')
this.temArr = this.temArr.filter(res => res.Id != pageId) // 清除缓存
this.curPage = null
}
}
}
3、 新建state.js
const state = {
message:'Hello World!'
}
exports.state = state
4、action同上
const action = {
messageChange: function (state,val) {
state.message = val
}
}
exports.action = action
实际上,上面的响应式管理器,已经运用在实际的项目中了。这其中做了不少关于检测的工作。但百密一疏,总会有疏漏的地方,如果有发现的地方,评论区给出,有奖奥😀