背景
互联网的打工人终于入职了!!!在公司里了解项目逻辑以及项目的一些相关概念,差不多有十天了,终于开始了第一个需求🤣。在很多的业务场景中,我们难免会有项目功能的“升级”,就可能会碰到要去提示用户一些信息,让用户按照提示去进行操作。
需求
能连续展示多条提示(每条的展示都是基于Alert),关闭一条自动展示下一条,直至没有。
思路分析
写一个Alert组件
一开始想到直接使用antd中的Alert组件。此时就会碰到第一个问题,antd中的样式“风格”和自己的项目不一致。ok,既然风格不一致,那就直接手写一个Alert组件吧(功能先和antd中的Alert组件功能保持一致)
约束组件属性类型
// 先进行属性约束
export type IconHelper = "success" | "info" | "warning" | "error";
export interface AlertProps {
title?: string;
type?: IconHelper;
// 是否显示辅助图标
showIcon?: boolean;
// 自定义关闭按钮
closeIcon?: ReactNode;
// 是否显示关闭按钮
closable?: boolean;
// 组件销毁时所出发的cb
afterClose?: () => void;
// 点击关闭按钮 所触发的cb
onClose?: (e: MouseEventHandler<HTMLButtonElement>) => {};
// 表示样式
style?: CSSProperties;
// 表示类名
classNames?: string;
// 表示内容节点
content: ReactNode;
}
书写Alert组件
// 关于style,我们其实可以直接使用本地项目中的tailwind中预定义的一些风格
// 使用到的cs 这个使用了classnames这个库,可以根据条件添加上类名
<div
ref={ref}
style={style}
className={cs([styles[type], classNames, styles.wrapper])}
>
{/* 左侧辅助图标 */}
<div className={cs([styles.left, { [styles.hidden]: showIcon }])}>
<RenderHelperIcon type={type} />
</div>
{/* 中间核心区域 */}
<div className={styles.content}>
{title && <div className={styles.title}>{title}</div>}
<div>{content}</div>
</div>
{/* 右侧关闭按钮 */}
{/* 可能会存在自定义的关闭图标 是一个ReactNode 我们无法添加事件 所以通过冒泡的形式 出发关闭事件*/}
<div
className={cs([styles.right, { [styles.hidden]: !closable }])}
onClick={closeComponent}
>
{closable && (closeIcon || <CloseOutlined />)}
</div>
</div>
让外部使用的时候获得更好的类型提示,并能使用ref拿到dom
// 在接口对应的文件中书写
declare const Alert: React.ForwardRefExoticComponent<
AlertProps & React.RefAttributes<HTMLDivElement>
>;
// 关于React.ForwardExoticComponent的实现
// 就是在组件上添加了propTypes属性 进而约束我们传递给组件的属性
interface ForwardRefExoticComponent<P> extends NamedExoticComponent<P> {
defaultProps?: Partial<P> | undefined;
propTypes?: WeakValidationMap<P> | undefined;
}
// 支持ref
forwardRef<HTMLDivElement, AlertProps>((props, ref) => {
return 上一步骤我们书写的东西(把ref添加上)
})
添加动画效果
// 直接使用react中的一个动画库 react-transition-group😀😀
const [visible, setVisible] = useState(true);
// 点击关闭按钮所出发的回调函数
const handleClose = useCallback(() => {
setVisible(false);
onClose && onClose();
});
const onExit = useCallback(() => {
afterClose && afterClose();
}, [])
/*
in 属性就是控制是否还显示
unmountOnExit 退出之后销毁dom
onExit 就是在消失后触发的回调
classNames 最终会和一些预定义好的名字进行拼接 让我们去自定义动画
*/
<CSSTransition
in={visible}
unmountOnExit
timeout={300}
onExit={onExit}
classNames="alert"
>
{我们上一步骤的Alert组件}
</CSSTransition>
总结
至此,我们就完成了一个简易版本的Alert版本了。但上面的功能在需求中还是受限的,如果提示消息存在多条了,很明显上面的组件就无法支持了(一开始,我想着去迭代传递给Alert组件的内容,然后控制一下关闭的回调来实现。但是就是因为原生的Alert存在CSSTransition,点击关闭按钮,先设置了visible属性,整个Alert就被关闭了)
实现需求
思路
写一个展示容器(外界传递什么就直接展示什么)。如果我们定义了一个Alert的数组,假设当前条Alert是可关闭的,此时就会有点击事件会冒泡,我们可以在容器组件上绑定事件对应去执行就好。事件触发了,也刚好是要设置下一个Alert进行展示!!
容器的实现
import { useState, useMemo } from "react";
interface IterReactNodes {
data: React.ReactNode[]; // 需要进行迭代展示的节点数组
afterClose?: () => void;
}
export const MultiAlert = forwardRef<HTMLDivElement, IterReactNodes>(
({ data }, ref) => {
const [current, setCurrent] = useState(data[0] || undefined);
const onClose = useMemo(() => {
let i = 1;
return () => {
setCurrent(() => {
const value = data[i++];
// 就是整体关闭的时候 所触发的函数 可以在接口中加上当前属性进行使用
// if (value === undefined) {
// afterClose && afterClose();
// }
return value;
});
};
}, [data]);
if (!current) {
return null;
}
return (
<div
ref={ref}
onClick={() => {
onClose();
}}
>
{current}
</div>
);
}
);
总结
既然上面的容器实现了,我们就可以根据从服务端的获取到需要提示给用户的消息提醒,针对每一个消息提示,使用Alert来包装一层,最终传递一个数组给我们的容器即可。
其他
欢迎看文章的同学进行批评指正呀!!🙈🙈🙈有更好的想法也可以给我提提!!聆听各位大佬的建议👀👀