【React】利用proxy实现发布订阅思路

前言

  • 参考的formily最小实现,记录下启发。

流程

  • 首先实现autorun与observable:
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;
	},
  • 将强刷传给tracker订阅:
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性能比较高,精确渲染更新任务少。

猜你喜欢

转载自blog.csdn.net/yehuozhili/article/details/125701111