【React】Message组件实现原理

前言

  • 以前自己写的message组件有bug,并且逻辑不全,我看很多人包括antd都是拿写好的rc-notification制作的,上次看了react-keepalive后,感觉自己水平又提升了,这玩意其实自己也能写出来。

原理

  • 首先梳理下这个原理。
  • 我以前写的那个message也能用,但是有个bug,虽然dom卸载了但是fiber没卸载掉。这就有可能造成内存泄漏(这个效果估计得触发超过10w遍且一直不刷新页面才能看得出来)。
  • 首要的难点就是组件卸载问题,所以组件它除了要卸载掉dom,fiber也得卸载掉。
  • 那么如何卸载?就是reactdom提供的unmount方法,这个方法比较恶心,我一开始写这个组件时候试过这个,但是效果有点不太对,我用法有问题,然后就没去卸载它。它应该是卸载reactDom.render的一整颗树。而render大家都用过,需要有挂载点,会清除挂载点里内容,所以这里我们肯定不能直接挂body上,不然把body都干掉了。所以这里的逻辑就是造个div,然后把内容挂这个div上,unmount就会卸载掉div里内容,然后你还得卸载掉这个div(因为这个div也是新加的)。也就是需要卸载2个东西。
  • 解决了卸载问题,还有个难点,就是一个可以自动获得高度的问题。这个问题有几种解决方法,有的是维护一个队列,然后算高度,我看了最简单最实用的方法是做一个空div定到顶部,fix定位,然后point-event:none让其不能选中,关键点在这,后面加的所有子div实际都是这个fix元素的子元素,这样就能让其自动获得对应高度了。
  • 其实这些解决方法都不是自己想的,也是看别人实现才知道的,深刻说明了很多实现都是要参考大量实践才能融会贯通。

代码

  • 做一个基本能使用的简单例子:
let wrap: HTMLElement;
export const createMessage = () => {
	return (content: ReactNode) => {
		if (typeof document === "undefined") {
			return;
		}
		if (!wrap) {
			//如果有的话,说明已经调用过这个函数了,这个空div就可以一直复用
			wrap = document.createElement("div");
			wrap.style.cssText = `line-height:
		1.5;text-align:
		center;color: #333;
		box-sizing: border-box;
		margin: 0;
		padding: 0;
		list-style: none;
		position: fixed;
		z-index: 100000;
		width: 100%;
		top: 16px;
		left: 0;
		pointer-events: none;`;
		if (wrap) {
			document.body && document.body.appendChild(wrap); //挂body上
		}
		}

		
		const divs = document.createElement("div");
		wrap.appendChild(divs);
		ReactDom.render(
			<Message rootDom={wrap} parentDom={divs} content={content} />,
			divs
		);
	};
};

export type MessageProps = {
	rootDom: HTMLElement; //这个用来干掉parentDom 这个可以常驻
	parentDom: Element | DocumentFragment; //这个是挂载点 要unmount卸载 完毕后卸载挂载点 注意!一共2步卸载,别漏了
	content: ReactNode;
};

export function Message(props: MessageProps) {
	const { rootDom, parentDom, content } = props;

	const unmount = useMemo(() => {
		return () => {
			if (parentDom && rootDom) {
				unmountComponentAtNode(parentDom);
				rootDom.removeChild(parentDom);
			}
		};
	}, [parentDom, rootDom]);

	useEffect(() => {
		setTimeout(() => {
			unmount();
		}, 2000);
	}, [unmount]);
	return <div style={{ background: "red" }}>{content}</div>;
}
  • 注释都写上了,至于为什么我要做成柯里化调用,因为这个还得结合选项再封装一层。
  • 测试用例:
	<div>
		<Button onClick={() => createMessage()("111")}>click</Button>
		<Button onClick={() => createMessage()("222")}>click</Button>
	</div>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

业火之理

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值