前言
- 以前自己写的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) {
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);
}
}
const divs = document.createElement("div");
wrap.appendChild(divs);
ReactDom.render(
<Message rootDom={wrap} parentDom={divs} content={content} />,
divs
);
};
};
export type MessageProps = {
rootDom: HTMLElement;
parentDom: Element | DocumentFragment;
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>