首先在body下创建一个div将其命名为message-dialog,后续使用的message都会挂载到这个div下面。react18中向dom渲染react组件的api:createRoot会将dom下面所有的node节点替换成挂载的react组件,所以当需要使用message的时候我们应该再创建一个div让这个div挂载组件再使用appendChildren将这个div挂载到message-dialog下。
import Loading from '@/components/Loading/Loading';
import { forwardRef, useEffect, useRef } from 'react';
import { createRoot, Root } from 'react-dom/client';
import { BsExclamationCircleFill, BsFillXCircleFill } from 'react-icons/bs';
import { FaCheckCircle } from 'react-icons/fa';
import { message_propsInterface } from './interface';
import styles from './Message.less';
const Message = forwardRef((props: message_propsInterface, ref: any) => {
const {id,root,duration=3, type, content, messageStyle = {} } = props;
useEffect(()=>{
if(duration){
setTimeout(()=>{
clearMessage();
},duration*1000)
}
},[])
const iconSize = '16px';
const clearMessage = function(){
message.clear({
id,
root
});
};
const renderMessageType = function (
type?: 'success' | 'error' | 'warning' | 'loading',
) {
let typeIcon = null;
switch (type) {
case 'success':
typeIcon = (
<FaCheckCircle
size={iconSize}
color="var(--success_color)"
></FaCheckCircle>
);
break;
case 'warning':
typeIcon = (
<BsExclamationCircleFill
size={iconSize}
color="var(--waring_color)"
></BsExclamationCircleFill>
);
break;
case 'error':
typeIcon = (
<BsFillXCircleFill
size={iconSize}
color="var(--error_color)"
></BsFillXCircleFill>
);
break;
case 'loading':
typeIcon = (
<Loading
loadingStyle={{
width: iconSize,
height: iconSize,
}}
background="transparent"
size={iconSize}
border="3px"
loadingTextShow={false}
></Loading>
);
default:
typeIcon = <></>;
}
return <span className={styles.typeIcon}>{typeIcon}</span>;
};
const returnStyles = function () {
return {
...props.messageStyle,
};
};
return (
<article className={styles.Message}>
<p className={styles.content} style={returnStyles()}>
{renderMessageType(type)}
{content}
</p>
</article>
);
});
let messageCount = 0;
const message: {
messageDialogId:string,
open: Function;
clear:Function;
} = {
messageDialogId:'message-dialog',
open: function (props?: message_propsInterface | undefined) {
let dialog: HTMLElement | null = document.getElementById(this.messageDialogId);
if (!dialog) {
dialog = document.createElement('div');
dialog.id = this.messageDialogId;
dialog.className = '';
dialog.setAttribute(
'style',
`
position:fixed;
top:0;
height:auto;
width:100%;
`,
);
document.body.appendChild(dialog);
}
messageCount = messageCount + 1;
const id = 'message-' + messageCount;
let currentMessage = document.getElementById(id);
if (!currentMessage) {
currentMessage = document.createElement('div');
currentMessage.id = id;
currentMessage.className = '';
dialog.appendChild(currentMessage);
let currentMessageRoot = createRoot(currentMessage);
if(!props){
props = {
id,
root:currentMessageRoot
}
}else{
props.id = id;
props.root = currentMessageRoot;
}
currentMessageRoot.render(<Message {...props}></Message>);
}
},
clear:function(messageInfo:{
id:string,
root:Root
}){
messageInfo.root.unmount();
let dialog: HTMLElement | null = document.getElementById(this.messageDialogId);
let removeMessage = document.getElementById(messageInfo.id);
if(dialog&&removeMessage){
//移出过期message
dialog.removeChild(removeMessage);
//当dialog没有子节点时移除
if(!dialog.hasChildNodes()){
document.body.removeChild(dialog);
}
}
}
};
export { message, Message };
.Message{
width: auto;
display: flex;
justify-content: center;
}
.typeIcon{
margin-right: var(--size-small);
}
.content{
display: flex;
align-items: center;
margin: var(--size-small);
border-radius: 8px;
min-width: 88px;
height: 40px;
padding: 8px;
background: var(--background);
box-shadow: 0px 0px 8px var(--boxShadowColor);
margin-left: var(--size-small);
color: var(--color);
}
import { CSSProperties } from "react"
import { Root } from "react-dom/client"
interface message_propsInterface{
type?:'success'|'error'|'warning'|'loading',
content?:string,
messageStyle?:CSSProperties,
id?:string,
root?:Root,
duration?:number,
}
export{
message_propsInterface
}