React 组件之 Drawer 组件

1,841 阅读2分钟

这次我们来实现一个常用的 Drawer 组件,this带你来体验体验, Go, 话不多说,先放图片

image.png

Drawer 组件实现,基础属性比较简单,getContainer 的实现需要用到 react 的 Portals api 如果对Portal 还不了解的, 可以先看下官方文档 zh-hans.reactjs.org/docs/portal…

通常来讲,当你从组件的 render 方法返回一个元素时,该元素将被挂载到 DOM 节点中离其最近的父节点

使用

ReactDOM.createPortal(
  this.props.children,
  domNode  // 要挂载的元素
);

然后就来实现我们的Drawer 组件


/**
 * @param {visible} 控制Drawer显示
 *  onClose 关闭回调
 *  title 标题
 *  width 宽度
 *  zIndex zIndex
 *  placement 抽屉方向
 *  mask 是否展示遮罩
 *  maskClosable 点击遮罩是否关闭
 *  closable 是否显示右上角关闭按钮
 *  destroyOnClose 关闭抽屉销毁子元素
 *  getContainer 指定 Drawer 挂载的 HTML 节点, 可以将抽屉挂载在任何元素上
 *  drawerStyle 能自定义抽屉弹出层样式
 */

import { useState, useEffect } from 'react';
import styles from './index.less';
import ReactDOM from 'react-dom';
import classnames from 'classnames';

interface IpropsDrawer {
  visible: boolean;
  title: React.ReactNode;
  width: number;
  onClose: () => void;
  zIndex: number;
  placement: string;
  mask: boolean;
  maskClosable: boolean;
  closable: boolean;
  destroyOnClose: boolean;
  getContainer: HTMLElement | false;
  drawerStyle: any;
}
type Iprops = IpropsDrawer;

export const Drawer: React.FC<Partial<Iprops>> = (props) => {
  const {
    visible,
    title = '标题',
    closable = true,
    width = 300,
    onClose,
    zIndex = 1000,
    placement = 'right',
    mask = true,
    maskClosable = true,
    destroyOnClose = true,
    getContainer = document.body,
    drawerStyle,
  } = props;

  // 控制关闭弹框清空弹框里面的元素
  const [clearContentDom, setClearContentDom] = useState(false);

  // 控制drawer 的显示隐藏
  const [drawerVisible, setDrawerVisible] = useState(visible);
  useEffect(() => {
    setDrawerVisible(() => {
      if (getContainer !== false && visible) {
        getContainer.style.overflow = 'hidden';
      }
      return visible;
    });
    if (visible) {
      setClearContentDom(false);
    }
  }, [visible]);

  // 点击弹框关闭
  const handleClose = () => {
    setDrawerVisible((prev) => {
      if (getContainer !== false && prev) {
        getContainer.style.overflow = 'auto';
      }
      return false;
    });
    onClose && onClose();
    if (destroyOnClose) {
      setClearContentDom(true);
    }
  };

  const drawerDom = (
    <div
      className={styles.drawerWarp}
      style={{
        width: drawerVisible ? '100%' : '0',
        zIndex,
        position: getContainer === false ? 'absolute' : 'fixed',
      }}
    >
      {mask && (
        <div
          className={styles.drawerMask}
          style={{ opacity: drawerVisible ? 1 : 0 }}
          onClick={maskClosable ? handleClose : undefined}
        ></div>
      )}
      <div
        className={classnames(styles.drawerContent, !drawerVisible ? styles.closeDrawer : '')}
        style={{
          width,
          [placement]: 0,
          ...drawerStyle,
        }}
      >
        {title && <div className={styles.titleDrawer}>{title}</div>}
        <div style={{ padding: 16 }}>{clearContentDom ? null : props.children}</div>
        {closable && (
          <span className={styles.closeDrawerBtn} onClick={handleClose}>
            X
          </span>
        )}
      </div>
    </div>
  );

  // getContainer 默认你是body, 如果为 false 则挂在最近的父节点上, 如果自己传入一个要
  // 挂在的对象下,则需要用到  ReactDOM.createPortal

  return getContainer === false && !getContainer
    ? drawerDom
    : ReactDOM.createPortal(drawerDom, getContainer);
};

添加对应的样式

.drawerWarp {
  position: absolute;
  top: 0;
  height: 100vh;
  overflow: hidden;
  .drawerMask {
    position: absolute;
    top: 0;
    right: 0;
    bottom: 0;
    left: 0;
    background-color: rgba(0, 0, 0, 0.5);
    transition: all 0.3s;
  }

  .drawerContent {
    position: absolute;
    top: 0;
    height: 100%;
    // padding: 16px;
    background-color: #fff;
    box-shadow: 0 0 20px rgba(0, 0, 0, 0.2);
    transition: all 0.3s;
    .titleDrawer {
      padding: 16px;
      color: #000000d9;
      font-weight: 500;
      font-size: 16px;
      border-bottom: 1px solid #f0f0f0;
    }
    .closeDrawerBtn {
      position: absolute;
      top: 16px;
      right: 16px;
      padding-left: 16px;
      color: #ccc;
      font-size: 16px;
      cursor: pointer;
    }
  }

  .closeDrawer {
    transform: translateX(100%);
  }
}

来使用一下

const [open2, setOpen2] = useState(false);

<Drawer visible={open2} onClose={() => setOpen2(false)}>
  输入
  <Input />
</Drawer>

大功告成 !!! 如果对你有帮助,就请点个赞吧👍