简易message实现,可以直接复制粘贴

545 阅读3分钟

前言

message组件大家应该都不陌生,最常用的就是antd的Message组件。如果项目没有引用antd,我们如何快速实现呢?

正文

功能

  • 调用方式message.warn ()、message.success()
  • 支持页面同时存在多个Message
  • 定时消失

实现思路

  1. 状态管理:我们需要一个地方来存储所有消息的状态信息,包括消息文本、类型(如成功、警告、错误)、是否显示、以及显示的时间长度。
  2. 组件渲染:需要一个组件来渲染消息。这个组件应该能动态接收消息数据,并在合适的时候显示或隐藏消息。
  3. 消息控制:我们需要一个接口(如函数)来添加消息到状态中,并在一段时间后自动移除消息。

代码实现

创建消息项组件

message.tsx (组件渲染)

import React from 'react';
import classnames from 'classnames';
import {
  CheckCircleFilled, CloseCircleFilled, ExclamationCircleFilled, InfoCircleFilled,
} from '@ant-design/icons';
import styles from './index.less';

interface messageProps {
  onHide: () => void;
  type: 'success' | 'error' | 'warning' | 'info';
  content: string
}

function TypeIcon(props: { type: 'success' | 'error' | 'warning' | 'info'}) {
  const { type } = props;

  switch (type) {
    case 'success':
      return <CheckCircleFilled className={styles.checkCircleFilled} />;
    case 'error':
      return <CloseCircleFilled className={styles.closeCircleFilled} />;
    case 'warning':
      return <ExclamationCircleFilled className={styles.exclamationCircleFilled} />;
    case 'info':
      return <InfoCircleFilled className={styles.infoCircleFilled} />;
    default:
      return null;
  }
}

function Message(props: messageProps) {
  return (
    <div
      className={classnames(styles.animation)}
      // 动画结束后移除对应message
      onAnimationEnd={props.onHide}
    >
      <TypeIcon type={props.type} />
      {props.content}
    </div>
  );
}

export default React.memo(Message);

//message出现、消失动画
.animation{
	animation: message 2s linear 1;
  opacity: 0;
  transform: translateY(-5px);
}
@keyframes message {
  0%, 100% {
    opacity: 0;
    transform: translateY(-5px);
  }
  5%, 95% {
    display: inline-block;
    padding: 5px 10px;
    background: #fff;
    border-radius: 6px;
    box-shadow: 0 3px 6px -4px #0000001f, 0 6px 16px #00000014, 0 9px 28px 8px #0000000d;
    pointer-events: all;
    opacity: 1;
    transform: translateY(0);
    margin-bottom: 5px;
  } 
}

.checkCircleFilled{ 
  color: #52c41a;
  margin-right: 8px;
}
.closeCircleFilled {
  color: rgb(255, 77, 79);
  margin-right: 8px;
}
.exclamationCircleFilled{
  color: rgb(250, 173, 20);
  margin-right: 8px;
}
.infoCircleFilled{
  color: rgb(24, 144, 255);
  margin-right: 8px;
}

创建消息容器组件:

MessageWrapper.tsx (状态管理、消息控制)

import React from 'react';
import Message from './message'; //
import styles from './index.less';

export interface MessageList {
  content: string;
  type: 'success' | 'error' | 'warning' | 'info';
  key: string;
}

interface MessageWrapperState {
  list: MessageList[];
}

class MessageWrapper extends React.PureComponent<{}, MessageWrapperState> {
  state: MessageWrapperState = {
    list: [],
  };

  public add = (params: MessageList): void => {
    this.setState(prevState => ({
      list: [...prevState.list, params],
    }));
  };

  public handleHide = (msg: MessageList): void => {
    this.setState(prevState => ({
      list: prevState.list.filter(item => item.key !== msg.key),
    }));
  };

  render(): JSX.Element {
    return (
      <div className={styles.messageWrapper}>
        {
          this.state.list.map(item => (
            <Message
              key={item.key}
              type={item.type}
              content={item.content}
              onHide={() => this.handleHide(item)}
            />
          ))
        }
      </div>
    );
  }
}

export default MessageWrapper;

.messageWrapper {
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  top: 40px;
  z-index: 1;
  display: flex;
  flex-direction: column;
  white-space: nowrap;
}

封装消息控制

message.success形式调用

手动挂载Message组件
import React, { useCallback, useMemo, useRef } from 'react';
import MessageWrapper from './messageWrapper';

export type Type = 'success' | 'error' | 'warning' | 'info';
export interface MessageList {
  content: string;
  type: Type;
  key: string;
}

export type MessageType = Record<Type, (content: string) => void>;

const getUniqueKey = (): string => {
  return Date.now().toString(36) + Math.random().toString(36).substr(2);
};

function useMessage() {
  const messageWrapperRef = useRef<MessageWrapper>(null);

  const MemoizedMessageWrapper = useMemo(() => <MessageWrapper ref={messageWrapperRef} />, []);

  const addMessage = useCallback((type: MessageList['type'], content: string) => {
    if (messageWrapperRef.current) {
      messageWrapperRef.current.add({
        key: getUniqueKey(),
        content,
        type,
      });
    }
  }, []);

  const message = useMemo(() => {
    return (
      {
        success: (content: string) => addMessage('success', content),
        error: (content: string) => addMessage('error', content),
        warning: (content: string) => addMessage('warning', content),
        info: (content: string) => addMessage('info', content),
      }
    );
  }, [addMessage]);

  return {
    message,
    MessageWrapper: MemoizedMessageWrapper,
  };
}

export default useMessage;

使用

import useMessage from './useMessage';

const { message, MessageWrapper } = useMessage();

// 父组件
<LevelContext.Provider value={message}>
  {MessageWrapper}
</LevelContext.Provider>

// 子组件
const { message } = useContext(LevelContext);
message?.warning('waring...');


自动挂载Message组件(antd实现方式)
import React from 'react';
import ReactDOM from 'react-dom';
import MessageWrapper, { MessageList } from './messageWrapper';

export const message = (() => {
  const messageWrapperRef = React.createRef<MessageWrapper>();
  let container = document.getElementById('message-container');
  if (!container) {
    container = document.createElement('div');
    container.setAttribute('id', 'message-container');
    document.body.appendChild(container);
  }

  ReactDOM.render(
    <MessageWrapper ref={messageWrapperRef} />,
    container,
  );

  const getUniqueKey = (): string => {
    return Date.now().toString(36) + Math.random().toString(36).substr(2);
  };

  const cleanup = () => {
    if (container) {
      ReactDOM.unmountComponentAtNode(container);
      container.remove();
      container = null;
    }
  };

  return {
    warning: (content: string) => {
      messageWrapperRef.current?.add({
        key: getUniqueKey(),
        content,
        type: 'warning',
      } as MessageList);
    },
    error: (content: string) => {
      messageWrapperRef.current?.add({
        key: getUniqueKey(),
        content,
        type: 'error',
      } as MessageList);
    },
    success: (content: string) => {
      messageWrapperRef.current?.add({
        key: getUniqueKey(),
        content,
        type: 'success',
      } as MessageList);
    },
    destroy: cleanup,
  };
})();

这里使用立即执行函数在代码定义时即刻初始化(初始化操作仅运行一次),并且形成闭包确保 messageWrapperRefcontainer唯一且一致。从而保证所有操作发生在同一个MessageWrapper 实例上。

使用

<div
  id="message-container"
>
  <div
    onClick={() => message.success('成功')}
  > 
    按钮
  </div>
</div>

感谢您的阅读,有不足之处请为我指出!