【React】如何实现并封装一个Popup组件(tsx)

写在前面:

在移动端展示数据时,常常会用到popup组件展示,本文实现一个可以上下拖动并且超出视口会自动回弹的popup组件

效果如下:

实现如下:

安装依赖库

首先引入antdreact-draggable来。

封装步骤:

  1. 定义Popup组件。这是一个函数式组件,接受onClosewidget作为参数。在组件内部,通过调用useState钩子函数创建了一个名为position的状态变量,并初始化{ x: 0, y: 0 }position用于跟踪弹出窗口的位置信息。

  2. 定义一个handleDrag函数,用于处理拖拽事件。当拖拽事件发生时,该函数会根据拖拽的位置更新position的状态。同时,通过一些条件判断限制了弹出窗口的垂直拖拽范围。

  3. 使用StyledPopup组件作为根容器,并嵌套了其他子元素。其中,使用了Draggable组件实现了弹出窗口的可拖拽功能,handle=".handle-area"表示只能通过拖拽.handle-area类的元素来进行拖拽。在拖拽过程中,会触发handleDrag函数来更新position的状态。

代码如下:

tsx 复制代码
import { Table } from 'antd';
import React, { useState } from 'react';
import Draggable from 'react-draggable';
import styled from 'styled-components';
import closeSvg from '../../../../assets/images/close.svg';
import { Widget } from '../../types/widgetTypes';

interface PopupProps {
  onClose: () => void;
  widget: Widget | undefined;
}

const Popup: React.FC<PopupProps> = ({ onClose, widget }) => {
  const closePopup = () => {
    onClose();
  };
  const tableData = widget?.config?.content?.dataChart?.config?.sampleData;

  const [position, setPosition] = useState({ x: 0, y: 0 });
  const handleDrag = (e, ui) => {
    if (ui.y < 0 && ui.y < -window.innerHeight * 0.48) {
      ui.y = -window.innerHeight * 0.48;
    } else if (ui.y > 0 && ui.y > window.innerHeight * 0.45) {
      ui.y = window.innerHeight * 0.45;
    }
    setPosition({ x: 0, y: ui.y });
  };
  return (
    <StyledPopup>
      <Draggable
        handle=".handle-area"
        axis="y"
        onDrag={handleDrag}
        position={position}
      >
        <div className="popup">
          <div className="handle-area">
            <div className="popup-handle"></div>
          </div>
          <div className="popup-header">
            <span>查看数据</span>
            <img onClick={closePopup} src={closeSvg} alt="Close" />
          </div>
          <div className="popup-content">
            {
              <TableWrapper>
                <Table
                  size="small"
                  dataSource={tableData?.rows}
                  columns={tableData?.columns?.map((col, index) => ({
                    key: col.name,
                    title: col.name,
                    dataIndex: index,
                  }))}
                  bordered
                />
              </TableWrapper>
            }
          </div>
        </div>
      </Draggable>
    </StyledPopup>
  );
};
const TableWrapper = styled.div`
  padding: 10px;
`;
const StyledPopup = styled.div`
  position: fixed;
  top: 50%;
  width: 100vw;
  & .popup {
    display: flex;
    flex-direction: column;
    border-top-left-radius: 20px;
    border-top-right-radius: 20px;
    height: 150vh;
    background-color: #fff;

    .popup-handle {
      height: 5px;
      width: 10%;
      background-color: rgb(217, 217, 217);
      cursor: row-resize;
      border-radius: 5px;
      margin: 8px auto;
      margin-bottom: 16px;
    }

    .popup-header {
      position: relative;
      display: flex;
      flex-direction: row;
      justify-content: center;
      font-size: 20px;
      font-weight: 700;
      color: rgb(19, 23, 55);

      img {
        position: absolute;
        right: 5px;
        top: 50%;
        transform: translate(-50%, -50%);
        width: 36px;
        height: 36px;
      }
    }
  }

  .popup-content {
    display: flex;
    flex-direction: column;
    margin: 20px 15px;
    max-height: 80vh;
    overflow: auto;
  }
`;

export default Popup;

3.使用参考:

比如可以在打开组件的函数、tsx内,传入数据以及打开的boolen值,并暴露一个onclose函数实现关闭popup的解构

tsx 复制代码
   const [popupOpen, setPopupOpen] = useState(false);
    const [popupWidget, setPopupWidget] = useState<Widget>();
    const onClosePopup = () => {
      setPopupOpen(false);
    };
onWidgetOpenPopup: (widget: Widget) => {
          setPopupOpen(true);
          setPopupWidget(widget);
        },
return (
        ...
        {popupOpen && <Popup onClose={onClosePopup} widget={popupWidget} />
        ...}
        
    );
相关推荐
触底反弹几秒前
💡 React 父子组件通信:一个进度条教会我的 5 件事
前端·react.js·面试
咩咩啃树皮1 分钟前
第44篇:Vue3侦听器完全精讲——watch与watchEffect区别、深度监听、异步业务落地
前端·javascript·vue.js
Python私教8 分钟前
前端转 AI 全栈:别只做聊天框,SSE 与审批流才是分水岭
前端·人工智能
漂移的电子12 分钟前
解决Vue3 + TypeScript + Vite搭建的项目,动态路由页面加载失败的最隐蔽原因
javascript·ubuntu·typescript
前鼻音太阳熊26 分钟前
【MES系统】- 工业HMI状态机可视化实践:从手写SVG到Cytoscape.js的技术选型与踩坑
开发语言·javascript·ecmascript
晓得迷路了1 小时前
栗子前端技术周刊第 139 期 - Nuxt 4.5、Vue 3.6 RC、Angular 发布节奏...
前端·javascript·vue.js
鱼樱前端1 小时前
AI 会不会取代前端?我看了 2026 年 7 月整个市场,给你一个不吓人的答案
前端·程序员·ai编程
এ慕ོ冬℘゜1 小时前
原生 select 下拉框搜索失效踩坑:文本搜索与 ID 匹配不对应问题排查
前端·javascript·html
程序员爱钓鱼1 小时前
Rust Result 详解:可靠的错误处理机制
前端·后端·rust
鱼樱前端2 小时前
别再"学工具"了,先搭你的 AI 工作流
前端·ai编程·前端工程化