React 封装命令式弹窗

概要: 使用 React 封装一个命令式弹窗组件, 弹窗中包含一个自定义的表单组件, 实现表单数据搜集功能.

效果图:

底部的按钮属于弹窗组件, 点击确定可以搜集表单中输入的数据.

React 以及相关组件的版本

复制代码
    "@ant-design/icons": "^5.6.1",
    "@ant-design/v5-patch-for-react-19": "^1.0.3",
    "antd": "^5.27.0",
    "react": "^19.1.0",
    "react-dom": "^19.1.0",
    "react-router-dom": "^7.8.1",

1. withForm.js

定义一个高阶函数, 传入表单组件, 返回一个新组件

复制代码
import { Modal } from "antd";
import { useState, useRef, useEffect } from "react";

// 将表单组件作为入参
const withForm = (FormCmp) => {
  const CmpWithForm = ({ visible, title, okText, okTrigger, cancelTrigger, afterClose }) => {
    const [isOpen, setIsOpen] = useState(visible);
    const formRef = useRef(null);
    const okHandler = () => {
      setIsOpen(false);
      // 获取表单数据
      const formData = formRef.current.getFormData();
      if (okTrigger) {
        // 将表单数据传给外部的处理函数
        okTrigger(formData);
      }
    }
    const cancelHandler = () => {
      setIsOpen(false);
      if (cancelTrigger) {
        cancelTrigger();
      }
    }

    useEffect(() => {
      if (!isOpen) {
        // 关闭后销毁dom
        afterClose(); 
      }
    }, [isOpen])

    return (
      <Modal
        title={title}
        open={isOpen}
        onOk={okHandler}
        onCancel={cancelHandler}
        okText={okText}
        maskClosable={false}
      >
        <div style={{ marginTop: '40px', marginRight: '40px' }}>
          <FormCmp ref={formRef} />
        </div>
      </Modal>
    )

  }

  return CmpWithForm;
}

export default withForm;
2. createModalWithForm.js

使用 render 函数手动渲染组件, 实现"命令式"调用的功能

复制代码
import { createRoot } from 'react-dom/client';
import { createElement } from "react";
import withForm from './withForm';

const createModalWithForm = (formCmp, { title, okText, okTrigger }) => {

  // 销毁组件
  const destroyModal = () => {
    setTimeout(() => {
      root.unmount();
      container.parentNode.removeChild(container);
    }, 800)
  }

  // 创建DOM容器
  const container = document.createElement('div');
  document.body.appendChild(container);
  const root = createRoot(container);
  const ModalCmp = withForm(formCmp);
  // 创建jsx组件
  const jsxCmp = createElement(ModalCmp, {
    visible: true,
    title: title,
    okText: okText,
    okTrigger: okTrigger,
    afterClose: destroyModal
  })

  // 渲染组件
  root.render(jsxCmp);
}

export default createModalWithForm;
3. UserForm.jsx

自定义的表单组件, 使用 useImperativeHandle 将表单组件中定义的函数暴露出去, 供外部的父组件调用. 具体调用的方式见 withForm.js 中 okHandler 回调函数, 使用ref.

复制代码
import { useEffect, useImperativeHandle } from "react";
import { Form, Input, InputNumber } from "antd";

const formItemLayout = {
  labelCol: { span: 6 },
  wrapperCol: { span: 16 },
};

const UserForm = ({ref}) => {
  const [form] = Form.useForm();
  console.log(ref);

  // 初始化表单数据
  // useEffect(() => {
  //   if (initialValues) {
  //     form.setFieldsValue(initialValues);
  //   }
  // }, [form, initialValues]);

  const getFormData = () => {
    let formData = form.getFieldsValue();
    return formData;
  }

  // 将获取表单数据的方法暴露给父组件
  useImperativeHandle(ref, () => ({
    getFormData
  }))

  return (
    <Form
      form={form}
      {...formItemLayout}
      layout="horizontal"
    >
      <Form.Item
        name="name"
        label="姓名"
        rules={[
          { required: true, message: '请输入姓名' },
          { min: 2, message: '姓名至少2个字符' },
        ]}
      >
        <Input placeholder="请输入姓名" />
      </Form.Item>

      <Form.Item
        name="age"
        label="年龄"
        rules={[
          { required: true, message: '请输入年龄' },
          { type: 'number', min: 0, max: 1000, message: '年龄必须在18-120之间' },
        ]}
      >
        <InputNumber placeholder="请输入年龄" style={{ width: '100%' }} />
      </Form.Item>
    </Form>
  )
}

export default UserForm;
4. MyModalTestV5.jsx

外部的测试组件, 点击按钮弹出弹窗

复制代码
import {Button} from 'antd';
import createModalWithForm from '../../../hooks/modal/v5/createModalWithForm';
import UserForm from '../../../hooks/modal/v5/UserForm';

export default function MyModalTestV5() {

  const showModal = () => {
    
    createModalWithForm(UserForm, {
      title: "新增用户",
      okText: "确定",
      okTrigger: okTrigger,
    })

  }

  // 点击确定按钮, 拿到表单数据, 可进行表单提交等操作!!!
  const okTrigger = (formData) => {
    console.log("MyModalTestV5 ", formData);
  }

  return (
    <div>
      <Button type="primary" onClick={showModal}>点我弹框 --V5</Button>
    </div>
  )
}
相关推荐
昔人'24 分钟前
`corepack` 安装pnpm
前端·pnpm·node·corepack
萌萌哒草头将军26 分钟前
pnpm + monorepo 才是 AI 协同开发的最佳方案!🚀🚀🚀
前端·react.js·ai编程
hboot1 小时前
💪别再迷茫!一份让你彻底掌控 TypeScript 类型系统的终极指南
前端·typescript
GISer_Jing2 小时前
深入拆解Taro框架多端适配原理
前端·javascript·taro
毕设源码-邱学长2 小时前
【开题答辩全过程】以 基于VUE的藏品管理系统的设计与实现为例,包含答辩的问题和答案
前端·javascript·vue.js
San30.2 小时前
深入理解 JavaScript:手写 `instanceof` 及其背后的原型链原理
开发语言·javascript·ecmascript
北冥有一鲲2 小时前
LangChain.js:RAG 深度解析与全栈实践
开发语言·javascript·langchain
用户28907942162713 小时前
Spec-Kit应用指南
前端
酸菜土狗3 小时前
🔥 手写 Vue 自定义指令:实现内容区拖拽调整大小(超实用)
前端
ohyeah3 小时前
深入理解 React Hooks:useState 与 useEffect 的核心原理与最佳实践
前端·react.js