react简单的message组件(使用js挂载)

26 阅读2分钟

message.gif 首先在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
}