搞轮子:不依赖外部DIY Toast 需要考虑什么

mask(遮罩)

import React, { FC, memo } from "react";
import classnames from "classnames";
import { MaskProps } from "./PropType";

const prefixCls = "jing-mask";

const Mask: FC<MaskProps> = (props) => {
  const { className, type, visible, style, onClick } = props;

  const classes = classnames(className, prefixCls, {
    [`${prefixCls}--${type}`]: !!type,
  });

  return visible ? (
    <div className={classes} style={style} onClick={onClick} />
  ) : null;
};

Mask.defaultProps = {
  type: "normal",
  visible: false,
};

export default memo(Mask);

缺点在于它

前提

animation(transition) 组件

mask(遮罩)组件(搞定)

portal 组件

最简单版本的

import React, { FC, useState, useEffect, memo } from "react";
import { createPortal } from "react-dom";
import { PortalProps } from "./PropType";

const Portal: FC<PortalProps> = (props) => {
  const { children, container, className } = props;

  const [containerEl] = useState(() => {
    const el = document.createElement("div");
    el.className += `jing-portal__container ${className}`;
    return el;
  });

  useEffect(() => {
    document.body.appendChild(container || containerEl);

    return () => {
      document.body.removeChild(containerEl);
    };
  }, []);

  return createPortal(children, containerEl);
};

export default memo(Portal);

定位 popup,弹出层

popup 组件

使用方式

当作静态函数使用

Toast(…)

Toast.loading()

组件本身要考虑什么功能

遮罩(mask)、内容(message)、是否禁止背景点击(forbidClick),支持自定义图标(icon)、自定义出现的位置(position)、是否在点击遮罩层后关闭(closeOnClickOverlay)

展示时长(duration)、关闭时的回调函数(onClose)

提供 Toast.success 表示成功,

Toast.fail 表示失败

Toast.loading 表示加载

单例模式

allowMultiple

梳理一下

Toast的基础是 Popup,Popup的基础是 Mask 和 Portal

Popup要做的就是弹出层,属于基础组件

Modal 要做之前 popup.alert 之类的事情(Vant 是 Dialog)

Toast 要实例化

无论是 Toast 还是 Modal 都需要使用静态方法调用

Modal 可以大写

popup 和 portal 放一起

不可见的时候看不到元素,

可见的时候渲染元素

动画

react-transition-group 和 portal 的结合

因为 portal 是return 出的组件,所以不会有动画

需要做 animation

https://stackoverflow.com/questions/54672784/animating-react-transition-group-with-reactdom-createportal

有人说给他加 animationDuration

https://codesandbox.io/s/stupefied-bouman-ehszt?file=/src/components/Portal/index.js:517-526

一定会有 div

节点从有到无

image-20211215181234263

animatedVisible 成为 true,显示Portal 组件,先渲染父组件,再渲染子组件

先执行

破除魔咒,取消

useEffect(() => {
  mountContainer?.appendChild(containerEl);

  return () => {
    mountContainer?.removeChild(containerEl);
  };
}, []);

react-transition-group 的用法

http://reactcommunity.org/react-transition-group/transition#Transition-prop-onExited

Toast 不是一个组件,而是一个 ”方法“, Toast("提示内容")

怎么把一个 <Toast>提示文字<Toast> 写法的组件改造成 Toast("提示内容")

查看了别人做的 Toast,

先做个 React 版本的 Toast,即组件时写法,然后再将它作为基础组件使用,怎么使用,

生成一个 div(document.createElement(‘div’))

插入 dom 中(bodyContainer.appendChild(container))

ReactDOM.render() 渲染它

我们先完成 Toast 组件

Toast.loading()

需要完成 loading 组件

loading 之后

要 useloading

show

hide

目的是把它当作一个静态函数使用

Loading.

const loading = Loading.useLoading();

<Button
  size="xs"
  onClick={() => {
    loading.show();
    setTimeout(() => {
      loading.hide();
    }, 3000);
  }}
>
  开启
</Button>

这里有个思考角度,loading 需不需要被 popup 包裹,我的想法是按照实际需求来做组件,我们可以做成像弹出层那样,但是 loading 的用法,一般是作为 Toast 的子组件来使用,所以这里,useLoading 对我们不适用

当然,做这个是为了铺垫 useToast,它也需要具备 Toast.show() 的用法

show 的时候 ReactDOM.render 过程