一个需求下的“Alert组件”

1,152 阅读4分钟

背景

互联网的打工人终于入职了!!!在公司里了解项目逻辑以及项目的一些相关概念,差不多有十天了,终于开始了第一个需求🤣。在很多的业务场景中,我们难免会有项目功能的“升级”,就可能会碰到要去提示用户一些信息,让用户按照提示去进行操作。

需求

能连续展示多条提示(每条的展示都是基于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来包装一层,最终传递一个数组给我们的容器即可。

其他

欢迎看文章的同学进行批评指正呀!!🙈🙈🙈有更好的想法也可以给我提提!!聆听各位大佬的建议👀👀