React写一个 Modal组件

吐槽一波

最近公司的项目终于度过了混乱的前期开发,现在开始有了喘息时间可以进行"规范"的处理了。
组件的处理,永远是前端的第一大任务,尤其是在我们的ui库并不怎么可靠的情况下,各个组件的封装都很重要,而Modal作为最独立的组件,就先拿它开刀了。

Modal的ui

首先,我们要创建一个最基本的组件,一个fixed布局的ui布局
在这里插入图片描述

import React from "react";
import "./App.css";function App() {
  return (
    <div className="fixed bottom-0 top-0 isd_modal">
      <div className="isd_modal_container top-50 fixed">
        <div className="isd_modal_header">
          <div>Modal Title</div>
          <div className="icon">x</div>
        </div>
        <div className="isd_modal_body py-0 px-4">Modal content</div>
        <div className="isd_modal_footer">
          <div className="cancel_btn btn h-9">Cancel</div>
          <div className="confirm_btn btn h-9">Ok</div>
        </div>
      </div>
    </div>
  );
}export default App;


由于css样式不是关注的重点,所以只会在最后的地方贴上css。
上面的代码也没啥好讲的,ui也贴出来了。

封装组件

最基本的ui构建完成后,我们需要将它封装成为一个组件,并且将各个参数传进来,最基本的参数无非是title以及content的内容,外加点击ok按钮以及cancel按钮的回调,以及最重要的控制Modal的显隐,下面的例子也会添加这么几个参数。

import { FC } from "react";
import "./index.css";interface ModalComponentProps {
  isVisible: boolean;
  modalTitle: string;
  children: React.ReactNode;
  onOk: () => void;
  onClose: () => void;
}const ModalComponent: FC<ModalComponentProps> = ({
  isVisible,
  children,
  modalTitle,
  onClose,
  onOk,
}) => {
  return isVisible ? (
    <div className="fixed bottom-0 top-0 isd_modal">
      <div className="isd_modal_container top-50 fixed">
        <div className="isd_modal_header">
          <div>{modalTitle}</div>
          <div className="icon" onClick={onClose}>
            x
          </div>
        </div>
        <div className="isd_modal_body py-0 px-4">{children}</div>
        <div className="isd_modal_footer">
          <div className="cancel_btn btn h-9" onClick={onClose}>
            Cancel
          </div>
          <div className="confirm_btn btn h-9" onClick={onOk}>
            Ok
          </div>
        </div>
      </div>
    </div>
  ) : null;
};
export default ModalComponent;

组件接受了从外部传进来的各个参数,并且将其渲染在页面上,如果有一些特殊性的动作,比如会从外部切换显示隐藏,建议对isVisible属性做useEffect的监听。
外部使用该组件的方法如下:

  <ModalComponent
        isVisible={modalVisible}
        modalTitle={"title from App.tsx"}
        children={<div>content from App.tsx</div>}
        onOk={function (): void {
          console.log("onOk from App.tsx");
          setModalVisible(false);
        }}
        onClose={function (): void {
          console.log("onClose from App.tsx");
          setModalVisible(false);
        }}
      />
其中,modalVisible一般会使用useState去存储状态

  const [modalVisible, setModalVisible] = React.useState(false);
做完这些,我们还需要使用createPortal做为我们的点睛之笔,将整个组件挂载在body上,这是必须要做的。

const ModalComponent: FC<ModalComponentProps> = ({
  isVisible,
  children,
  modalTitle,
  onClose,
  onOk,
}) => {
  const renderModal = createPortal(
    <div className="fixed bottom-0 top-0 isd_modal">
      <div className="isd_modal_container top-50 fixed">
        <div className="isd_modal_header">
          <div>{modalTitle}</div>
          <div className="icon" onClick={onClose}>
            x
          </div>
        </div>
        <div className="isd_modal_body py-0 px-4">{children}</div>
        <div className="isd_modal_footer">
          <div className="cancel_btn btn h-9" onClick={onClose}>
            Cancel
          </div>
          <div className="confirm_btn btn h-9" onClick={onOk}>
            Ok
          </div>
        </div>
      </div>
    </div>
,
    document.body
  );
  return isVisible ? renderModal : null;
};

上述代码中,第八行使用了createPortal去将我们的Modal挂载在了body上。
css文件如下:

.bottom-0{
    bottom: 0;
  }
  .top-0{
    top: 0;
  }
  .top-50{
    top: 50%;
  
  }
  .fixed{
    position: fixed;
  }
  .py-0{
    padding: 0;
  }
  .px-4{
    padding-left: 8px;
    padding-right: 8px;
  }
  .isd_modal {
    left: 0;
    right: 0;
    background: rgba(0, 0, 0, 0.3);
    z-index: 10000;
  }
  
  .isd_modal .isd_modal_container {
    background-color: #ffffff;
    border-radius: 8px;
    min-width: 600px;
    max-width: 80vw;
    height: fit-content;
    width: fit-content;
    left: 50%;
    transform: translateX(-50%) translateY(-50%);
  }
  .isd_modal .isd_modal_container .isd_modal_header {
    height: 60px;
    padding: 16px;
    font-size: 20px;
    line-height: 28px;
    display: flex;
    align-items: center;
    justify-content: space-between;
  }
  
  .isd_modal .isd_modal_container .isd_modal_footer {
    height: 68px;
    margin-bottom: 16px;
    padding: 0px 16px;
    display: flex;
    align-items: center;
    justify-content: flex-end;
  }
  .isd_modal .icon {
    cursor: pointer;
  }
  .isd_modal .isd_modal_footer .btn {
    min-width: 78px;
    padding: 8px 16px;
    width: fit-content;
    font-size: 14px;
    border-radius: 4px;
    line-height: 20px;
    text-align: center;
    cursor: pointer;
  }
  .isd_modal .confirm_btn {
    background-color: #1274a1;
    color: #ffffff;
    border: 1px solid #ffffff;
    margin-left: 16px;
  }
  
  .isd_modal .cancel_btn {
    background-color: #fbfbfb;
    color: #242424;
    border: 1px solid #b8b8b8;
  }
  .isd_modal .icon_close {
    fill: #1b1b1b;
  }
  
组件的全部代码如下:
import { FC } from "react";
import "./index.css";
import { createPortal } from "react-dom";interface ModalComponentProps {
  isVisible: boolean;
  modalTitle: string;
  children: React.ReactNode;
  onOk: () => void;
  onClose: () => void;
}const ModalComponent: FC<ModalComponentProps> = ({
  isVisible,
  children,
  modalTitle,
  onClose,
  onOk,
}) => {
  const renderModal = createPortal(
    <div className="fixed bottom-0 top-0 isd_modal">
      <div className="isd_modal_container top-50 fixed">
        <div className="isd_modal_header">
          <div>{modalTitle}</div>
          <div className="icon" onClick={onClose}>
            x
          </div>
        </div>
        <div className="isd_modal_body py-0 px-4">{children}</div>
        <div className="isd_modal_footer">
          <div className="cancel_btn btn h-9" onClick={onClose}>
            Cancel
          </div>
          <div className="confirm_btn btn h-9" onClick={onOk}>
            Ok
          </div>
        </div>
      </div>
    </div>
,
    document.body
  );
  return isVisible ? renderModal : null;
};
export default ModalComponent;


Modal作为一个最基本而又最独立的组件,是必须要学会如何封装的!
在这里插入图片描述

公众号文章链接

求关注~ 希望能帮到你~​​

<think>好的,用户让我介绍一下如何封装Ant Design的Modal组件。首先,我需要明确用户的需求。他们可能是在React项目中使用Ant Design,但发现Modal组件需要重复编很多类似的逻辑,比如控制显示隐藏、确定和取消按钮的处理等。封装Modal组件可以帮助他们减少重复代码,提高开发效率。 接下来,我应该考虑如何开始封装。首先想到的是创建一个新的React组件,比如CustomModal,接收必要的props,如visible、onOk、onCancel等。然后,将这些props传递给Ant Design的Modal组件。但可能还有更多的配置项需要处理,比如标题、内容、按钮文字等。 然后,用户可能希望自定义Modal的内容。这时候需要使用children属性,允许父组件传递任意内容到Modal中。同时,可能需要处理默认值,比如如果用户没有传递某些props,应该设置合理的默认值,比如标题默认为“提示”,确定按钮文字为“确定”等。 另外,用户可能希望扩展功能,比如在点击确定按钮后执行异步操作,并在操作完成后自动关闭Modal。这时候需要在onOk处理函数中加入异步逻辑,可能需要使用async/await,并确保在操作完成后调用onOk和onClose。 还需要考虑样式和动画的自定义。Ant Design的Modal组件本身支持一些样式配置,比如宽度、类名、遮罩效果等,这些都应该在封装时暴露给父组件,让用户可以根据需要调整。 可能还需要处理Modal的挂载节点,比如getContainer属性,但大多数情况下使用默认值即可,除非有特殊需求。此外,键盘事件比如按ESC关闭,是否需要禁用,这也应该通过props来控制。 然后,我需要思考如何展示示例代码。应该分步骤来,首先展示基本的封装,然后逐步添加功能,比如默认值、异步操作、样式自定义等。代码需要符合React的最佳实践,使用函数组件和Hooks,比如useState和useEffect来处理状态和副作用。 同时,需要注意Ant Design版本的问题,比如Modal的visible属性在较新版本中可能改为open,需要提醒用户根据实际版本调整属性名。这也是常见的问题点,避免用户因为版本差异导致代码无法运行。 另外,要提到如何复用这个封装后的组件,比如在不同的父组件中引入CustomModal,传递不同的内容和配置,从而减少重复代码。可能还需要讨论是否需要使用forwardRef来暴露某些方法,但通常情况下,通过props控制已经足够。 最后,总结封装的好处,比如提高代码复用性、统一UI风格、简化逻辑处理等。同时,提醒用户根据实际需求进行调整,比如添加更多的props或自定义功能。 在编过程中,要确保符合用户提供的系统级指令,比如使用正确的中文,数学公式的格式(虽然这里可能用不到,但需要注意),结构清晰,分步骤说明。同时,代码部分要正确使用LaTeX的代码块格式,确保语法正确。</think>我将分步骤为您讲解如何封装Ant Design的Modal组件,并附上完整代码示例: 1. **基础封装** ```jsx import { Modal } from 'antd'; function CustomModal({ visible, onOk, onCancel, title, content, okText, cancelText }) { return ( <Modal title={title} visible={visible} onOk={onOk} onCancel={onCancel} okText={okText} cancelText={cancelText} > {content} </Modal> ); } ``` 2. **添加默认值** ```jsx function CustomModal({ visible = false, onOk = () => {}, onCancel = () => {}, title = '提示', content = '确认执行此操作吗?', okText = '确定', cancelText = '取消' }) { // 保持原有结构 } ``` 3. **支持自定义内容** ```jsx function CustomModal({ children, ...props }) { return ( <Modal {...props}> {children || props.content} </Modal> ); } // 使用示例 <CustomModal> <div>自定义内容</div> <p>支持任意React元素</p> </CustomModal> ``` 4. **增强功能 - 异步操作** ```jsx function CustomModal({ onAsyncConfirm, ...props }) { const [loading, setLoading] = useState(false); const handleOk = async () => { try { setLoading(true); await onAsyncConfirm(); props.onCancel(); } finally { setLoading(false); } }; return ( <Modal {...props} confirmLoading={loading} onOk={handleOk} /> ); } ``` 5. **完整封装方案** ```jsx import { Modal } from 'antd'; import { useState } from 'react'; function CustomModal({ visible = false, title = '系统提示', width = 520, closable = true, maskClosable = true, keyboard = true, className = '', okText = '确认', cancelText = '取消', onOk = () => {}, onCancel = () => {}, children, footer = null, destroyOnClose = true }) { const [confirmLoading, setConfirmLoading] = useState(false); const handleOk = async () => { try { setConfirmLoading(true); await onOk(); onCancel(); } finally { setConfirmLoading(false); } }; return ( <Modal title={title} visible={visible} width={width} closable={closable} maskClosable={maskClosable} keyboard={keyboard} className={`custom-modal ${className}`} okText={okText} cancelText={cancelText} onOk={handleOk} onCancel={onCancel} confirmLoading={confirmLoading} footer={footer} destroyOnClose={destroyOnClose} > {children} </Modal> ); } ``` **使用示例:** ```jsx // 父组件 function ParentComponent() { const [visible, setVisible] = useState(false); const handleSubmit = async () => { // 模拟API请求 await new Promise(resolve => setTimeout(resolve, 1000)); console.log('提交成功'); }; return ( <> <Button onClick={() => setVisible(true)}>打开弹窗</Button> <CustomModal visible={visible} onOk={handleSubmit} onCancel={() => setVisible(false)} title="用户协议" width={800} > <Form> <Form.Item label="用户名"> <Input /> </Form.Item> </Form> </CustomModal> </> ); } ``` **封装优势:** 1. 统一弹窗交互逻辑 2. 内置加载状态处理 3. 支持默认配置和自定义配置 4. 封装异步操作流程 5. 保持Ant Design原始API的同时扩展功能 6. 统一维护弹窗样式 **注意事项:** 1. Ant Design 4.x版本使用`visible`属性,5.x版本改为`open` 2. 复杂场景建议配合useImperativeHandle暴露方法 3. 样式覆盖推荐使用CSS-in-JS方案 4. 需要处理内存泄漏问题时启用destroyOnClose 可根据实际项目需求继续扩展以下功能: - 自定义页脚模板 - 全屏模式支持 - 多步骤弹窗 - 全局状态管理集成 - 动画效果增强
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值