前言
message组件大家应该都不陌生,最常用的就是antd的Message组件。如果项目没有引用antd,我们如何快速实现呢?
正文
功能
- 调用方式message.warn ()、message.success()
- 支持页面同时存在多个Message
- 定时消失
实现思路
- 状态管理:我们需要一个地方来存储所有消息的状态信息,包括消息文本、类型(如成功、警告、错误)、是否显示、以及显示的时间长度。
- 组件渲染:需要一个组件来渲染消息。这个组件应该能动态接收消息数据,并在合适的时候显示或隐藏消息。
- 消息控制:我们需要一个接口(如函数)来添加消息到状态中,并在一段时间后自动移除消息。
代码实现
创建消息项组件
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,
};
})();
这里使用立即执行函数在代码定义时即刻初始化(初始化操作仅运行一次),并且形成闭包确保 messageWrapperRef 和container 是唯一且一致。从而保证所有操作发生在同一个MessageWrapper 实例上。
使用
<div
id="message-container"
>
<div
onClick={() => message.success('成功')}
>
按钮
</div>
</div>
感谢您的阅读,有不足之处请为我指出!