前言
流程
const RawReactionMap = new WeakMap();
let currentReaction;
export function observable(value) {
return new Proxy(value, basehandler);
}
const basehandler = {
get(target, key) {
const result = target[key];
if (currentReaction) {
addRawReactionMap(target, key, currentReaction);
}
return result;
},
set(target, key, value) {
target[key] = value;
RawReactionMap.get(target)
?.get(key)
?.forEach((reaction) => {
reaction();
});
return true;
},
};
function addRawReactionMap(target, key, currentReaction) {
const reactionsMap = RawReactionMap.get(target);
if (reactionsMap) {
const reactions = reactionsMap.get(key);
if (reactions) {
const filterColumn = reactions.filter((v) => v !== currentReaction);
filterColumn.push(currentReaction);
reactionsMap.set(key, filterColumn);
} else {
reactionsMap.set(key, [currentReaction]);
}
} else {
const reactionsMap = new Map();
reactionsMap.set(key, [currentReaction]);
RawReactionMap.set(target, reactionsMap);
}
return reactionsMap;
}
export function autorun(tracker) {
const reaction = () => {
currentReaction = reaction;
tracker();
currentReaction = null;
};
reaction();
}
- 可以看见实际上是利用js不会并行执行,currentReaction表示正在执行的那个函数,利用proxy代理,在取时用map记录该绑定函数、对象的键值对。设置值时从map里取出绑定函数去执行。
- 就像我们做发布订阅一定要有用函数订阅过程,它这个就必须使用autorun让函数进入reaction记录上,类似与调用订阅函数,只是感觉比较隐蔽。
Observer
- react精确渲染只要包个Observer就行,我们通过上面的实现其实已经知道,只要将下面函数置于currentReaction即可。那么还缺个刷新,绑定强刷在执行时做个判断就行了。
const obs = observable({ name: "1" });
const tracker = () => {
console.log(obs.name);
};
autorun(tracker);
export default () => {
return (
<div>
<Observer>
{() => {
return (
<input
value={obs.name}
onChange={(e) => (obs.name = e.target.value)}
></input>
);
}}
</Observer>
</div>
);
};
- 制作Tracker进行赋值current并且传入参数为强刷:
export class Tracker {
constructor(scheduler) {
this.track._scheduler = scheduler;
}
track = (tracker) => {
currentReaction = this.track;
const result = tracker();
currentReaction = null;
return result;
};
}
- 修改前面拿map的set执行,当该函数有强刷时执行强刷:
set(target, key, value) {
target[key] = value;
RawReactionMap.get(target)
?.get(key)
?.forEach((reaction) => {
if (typeof reaction._scheduler === "function") {
reaction._scheduler();
} else {
reaction();
}
});
return true;
},
export function Observer(props) {
const [, forceUpdate] = useReducer((x) => x + 1, 0);
const trackerRef = useRef(null);
if (!trackerRef.current) {
trackerRef.current = new Tracker(forceUpdate);
}
return trackerRef.current.track(props.children);
}
总结
- 我们可以看见主要思路是代理对象set后执行订阅的函数,订阅主要利用放到currentReaction上,取值进入map,当set时取出map中所有函数进行执行。另外,这里面还需要考虑重复函数,退订等问题,并不是直接能拿来使用。
- 利用这种全局资源,可以在任意地方去获取自己想要的资源进行操作。
- 这个模式的好处有,订阅时接近无感知,发布不需要手动调用函数,同时proxy性能比较高,精确渲染更新任务少。