react+ant封装可拖拽(Modal)弹窗,继承官方属性。可进行拓展

本文介绍了如何使用React和AntDesign库创建一个可拖拽的Modal组件,该组件继承了AntDesignModal的官方属性,并允许用户自定义拖拽行为和边界。作者展示了如何使用useState、useRef和useEffecthooks来实现这个功能及其使用示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

react+ant封装可拖拽(Modal)弹窗,继承官方属性。可进行拓展
import { useState, useRef, useEffect } from "react";
import { Modal, ModalProps } from "antd";
import Draggable from "react-draggable";
import styles from "./index.module.less";

//ModalProps 继承官方modal属性,使用时属性提示
interface WebModal extends ModalProps {
  openDrag?: boolean; //是否开启弹窗的拖拽
  dragHandle?: boolean; //开启头部拖拽或者弹窗拖拽
}
const WebReactModal= ({
  openDrag = false,
  dragHandle = false,
  ...props
}: WebModal) => {
  const [position,setPosition]=useState({ x: 0, y: 0 })
  const [bounds, setBounds] = useState({
    left: 0,
    top: 0,
    bottom: 0,
    right: 0,
  }); //存储模态框的边界值
   const draggleRef = useRef<any>();
  //模态框开始移动计算莫太框的位置和边界
  const onStart = (event, uiData) => {
    const { clientWidth, clientHeight } = window?.document?.documentElement;
    //获取到模态框的位置,从而计算可以动的边界值
    const targetRect = draggleRef?.current?.getBoundingClientRect(); 
       setBounds({
      left: -targetRect?.left + uiData.x,
      right: clientWidth - (targetRect?.right - uiData.x),
      top: -targetRect?.top + uiData.y,
      bottom: clientHeight - (targetRect?.bottom - uiData.y),
    });
  };
  //因为设置了初始位置,所以添加模态框移动时更新位置
   const onDrag = (e, data) => {
    const { x, y } = data;
    setPosition({ x, y });
  };
  //监听模态框的关闭,恢复到初始的位置
  useEffect(() => {
    if (!props.open) {
      setPosition({ x: 0, y: 0 });
    }
  }, [props?.open]);
  return (
    <Modal
      maskClosable={false}
      className={styles.webModal}
      modalRender={(modal) => (
        <Draggable
          handle={dragHandle?"":'.ant-modal-header'}
          disabled={openDrag}
          onDrag={onDrag} 
          bounds={bounds}
          position={position}
          onStart={(event, uiData) => onStart(event, uiData)}
        >
         <div ref={draggleRef}>{modal}</div>
        </Draggable>
      )}
      {...props}
    >
      {props?.children}
    </Modal>
  );
};

export default WebReactModal;
//index.module.less样式文件
.webModal {
  :global {
    .ant-modal-content {
      .ant-modal-header {
        padding: 10px;
        background: #e5f0ff;
        border-bottom: 1px solid #f0f0f0;
        border-radius: 2px 2px 0 0;
      }
    }
    //标题样式,可根据自己需要修改样式
    .ant-modal-title {
      color: #566286;
      width: 100%;
      cursor: move;
      user-select: none;
      font-weight: bold;
    }
//关闭弹窗的X的样式
    .ant-modal-close-x {
      width: 42px;
      height: 42px;
      display: flex;
      align-items: center;
      justify-content: center;
    }
  }
}
//使用方法----记得应用组件哦
<WebModalwidth={1200} onOk={()=>{}} title={"测试弹窗"} open={openA} onCancel={()=>{setOpenA(false)}}>
  <div>存放弹窗中的内容</div>
</WebModal>
<think>好的,我现在需要帮助用户解决如何用Vue 3和React分别实现既能通过$Modal方法调用,又能以组件形式使用的Modal弹窗。首先,我需要回忆一下Vue和React的不同特性,以及如何在这两个框架中实现全局组件和方法调用。 对于Vue 3,我记得可以使用provide/inject或者app.config.globalProperties来添加全局方法。同时,组合式API和teleport组件可能也会用到。用户提到的类似Ant Design的可拖拽弹窗可能需要集成第三方库,比如vuedraggable,但用户给的引用里提到了react-draggable,可能Vue也有类似的库。不过用户的问题主要是两种调用方式,所以拖拽可能不是重点,但可能需要考虑。 在Vue中,实现$Modal方法调用,通常需要创建一个Vue实例,并将其挂载到DOM中。然后通过这个实例暴露open、close等方法。同时,组件形式的话,就是正常的组件使用,通过props控制显示隐藏。需要确保两种方式可以共存,并且状态同步。 对于React,由于没有全局实例的概念,可能需要使用Context或者全局状态管理,比如Redux,或者利用ReactDOM.render来动态渲染组件。不过React的函数组件和hooks更推荐使用状态驱动的方式。用户想要通过类似Modal.open()的方式调用,这可能需要用到ReactDOM.render来动态创建组件实例,或者使用一个全局的Provider来管理弹窗的状态。 然后需要参考用户提供的引用内容。引用1是关于Vue的弹窗实现,提到直接通过Vue.$confirm调用,这可能涉及到在Vue的原型上挂载方法。引用2提到在Vue和React封装弹窗和加载模块,特别是小型项目中的自定义需求。引用3是关于Antd React Modal的可拖拽实现,使用了react-draggable,不过用户的问题可能不需要拖拽功能,但可能需要考虑如何将Modal组件封装得更灵活。 现在需要分步骤思考: 首先,Vue 3的实现: 1. 创建Modal组件:使用teleport将内容传送到body下,确保层级正确。组件通过v-model控制显示,接收标题、内容等props。 2. 实现全局方法:在Vue应用实例上挂载$Modal对象,包含open、close等方法。这可以通过app.config.globalProperties来添加,或者在插件中提供。 3. 使用服务层管理状态:创建一个服务实例,维护弹窗的状态(visible),并通过reactive或ref来响应式更新。当调用$Modal.open()时,改变状态,触发组件重新渲染。 4. 动态渲染组件:可能需要在挂载应用时,将Modal组件挂载到DOM中,并通过服务层控制其显示。或者在每次调用open时,动态创建组件实例,但Vue推荐使用响应式状态控制。 可能的代码结构: - 创建Modal.vue组件,使用teleport。 - 创建modalService.js,导出state和open/close方法,使用Vue的响应式系统。 - 在main.js中将service挂载到app实例的globalProperties上,这样组件内可以通过inject获取,或者通过this.$Modal访问。 - 同时,在组件中使用Modal组件,通过v-model绑定service中的visible状态,并通过props传递内容。 然后,React的实现: 1. 创建Modal组件:使用Portal将内容渲染到body下,控制显示隐藏的props。 2. 实现全局方法调用:由于React没有直接的原型方法,可以使用一个全局的状态管理器,比如Context API,或者使用一个单例类来管理弹窗的状态。 3. 当调用Modal.open()时,更新状态,触发组件重新渲染。需要将Modal组件放置在应用的顶层,通过Context Provider来传递状态。 4. 或者使用ReactDOM.render动态渲染Modal组件,但需要注意在React 18之后,ReactDOM.render已被createRoot替代,且可能不太推荐这种方式,因为可能导致状态管理复杂。 可能的代码结构: - 创建Modal组件,使用ReactDOM.createPortal。 - 创建一个ModalContext,使用useState来管理visible和content。 - 导出一个useModal的hook,或者一个全局的ModalService类,包含open和close方法,更新context的状态。 - 在应用顶层包裹ModalProvider,将Modal组件渲染在这里,状态由context控制。 - 同时,允许用户直接使用<Modal>组件,通过props控制。 另外,两种框架都需要考虑的是,如何同时支持方法调用和组件形式。在Vue中,可能通过同一个服务层控制,而在React中,可能需要Context来同步状态。 现在,需要验证这些思路是否正确。比如在Vue中,是否可以通过服务层管理状态,然后在组件中使用v-model绑定,同时通过全局方法修改服务层的状态。是的,这样应该是可行的。 在React中,使用Context来管理全局的Modal状态,然后通过调用open方法更新context中的状态,从而显示对应的Modal内容。同时,也可以通过直接渲染<Modal>组件来控制。但是这样的话,两种方式可能共享同一个状态,导致冲突。或者需要将两种方式分开处理,比如全局方法使用独立的Modal实例,而组件形式使用自己的状态。这可能需要更复杂的设计,或者使用多个Providers。 或者,当使用全局方法时,动态创建Modal实例,并将其添加到DOM中,而组件形式的Modal是独立的状态。这可能更灵活,但需要处理多个Modal实例的情况。 可能React的实现中,更常见的做法是使用状态管理库(如Redux)来管理所有的Modal状态,然后通过dispatch action来打开或关闭Modal。但用户可能希望不依赖外部库,所以使用Context API更合适。 总结步骤: Vue 3实现: - 创建Modal组件,使用teleport,接收props。 - 创建可响应式的modalService,管理状态和内容。 - 将modalService挂载到app实例,通过app.config.globalProperties.$Modal = modalService。 - 在组件中使用v-if="modalService.visible",并通过modalService.content显示内容。 - 当调用$Modal.open(content)时,更新modalService的状态。 - 同时,用户也可以直接使用<Modal v-model="localVisible">,通过本地状态控制。 React实现: - 创建Modal组件,使用createPortal。 - 创建ModalContext,保存visible和content状态,并提供open和close方法。 - 在根组件使用ModalProvider,渲染全局的Modal组件,绑定到Context的状态。 - 导出useModal hook,让组件可以通过const { open } = useModal()来调用方法。 - 同时,用户也可以直接使用<Modal open={localOpen} onClose={...}>,通过本地状态控制。 这样,两种方式都可以实现。当通过open方法调用时,更新Context中的状态,显示全局的Modal;而组件形式则通过本地状态控制独立的Modal实例。 可能还需要考虑多个Modal同时存在的情况,比如全局的和本地的互不影响,这可能需要不同的处理方式,但用户的问题可能仅需要两种调用方式,不一定是同时存在。 现在需要将这些思路转化为具体的代码步骤,并确保两种调用方式都可行,同时语法正确,符合Vue 3和React的最佳实践。 可能需要注意的点: 在Vue中,使用teleport将Modal的内容传送到body下,避免父组件的样式影响。同时,服务层需要使用响应式对象,如ref或reactive,确保状态变化触发视图更新。 在React中,使用createPortal将Modal渲染到body下,避免父组件的overflow:hidden等问题。Context需要正确传递状态和方法,函数组件中使用useContext来获取。 现在,编写具体的代码示例,并验证是否可行。</think>### Vue 3实现方案 #### 1. 组件形式实现 ```vue <!-- Modal.vue --> <template> <teleport to="body"> <div v-if="modelValue" class="modal-mask"> <div class="modal-container"> <div class="modal-header"> <h3>{{ title }}</h3> <button @click="$emit('update:modelValue', false)">×</button> </div> <div class="modal-content"> <slot>{{ content }}</slot> </div> </div> </div> </teleport> </template> <script setup> defineProps({ modelValue: Boolean, title: String, content: String }) </script> ``` #### 2. 全局服务层 ```javascript // modalService.js import { ref } from 'vue' const state = ref({ visible: false, title: '', content: '' }) export const modalService = { open: (config) => { state.value = { ...config, visible: true } }, close: () => { state.value.visible = false }, state } ``` #### 3. 全局挂载 ```javascript // main.js import { createApp } from 'vue' import App from './App.vue' import Modal from './Modal.vue' import { modalService } from './modalService' const app = createApp(App) app.component('Modal', Modal) app.config.globalProperties.$Modal = modalService app.mount('#app') ``` #### 4. 两种调用方式 ```vue <!-- 组件形式 --> <Modal v-model="localVisible" title="提示" content="这是组件调用"/> <!-- 方法调用 --> <script setup> import { getCurrentInstance } from 'vue' const { $Modal } = getCurrentInstance().appContext.config.globalProperties const openModal = () => { $Modal.open({ title: '系统提示', content: '这是方法调用的弹窗' }) } </script> ``` ### React实现方案 #### 1. 创建上下文 ```jsx // ModalContext.jsx import React, { createContext, useState } from 'react' export const ModalContext = createContext() export const ModalProvider = ({ children }) => { const [state, setState] = useState({ visible: false, content: '', title: '' }) const open = (config) => setState({ ...config, visible: true }) const close = () => setState(prev => ({ ...prev, visible: false })) return ( <ModalContext.Provider value={{ open, close }}> {children} <Modal {...state} onClose={close} /> </ModalContext.Provider> ) } ``` #### 2. Modal组件 ```jsx // Modal.jsx import ReactDOM from 'react-dom' export const Modal = ({ visible, title, content, onClose }) => { return visible && ReactDOM.createPortal( <div className="modal-mask"> <div className="modal-container"> <div className="modal-header"> <h3>{title}</h3> <button onClick={onClose}>×</button> </div> <div className="modal-content">{content}</div> </div> </div>, document.body ) } ``` #### 3. 应用入口 ```jsx // main.jsx import { ModalProvider } from './ModalContext' ReactDOM.createRoot(document.getElementById('root')).render( <ModalProvider> <App /> </ModalProvider> ) ``` #### 4. 两种调用方式 ```jsx // 组件形式 <Modal visible={localVisible} title="组件弹窗" content="这是组件调用方式" onClose={() => setLocalVisible(false)} /> // 方法调用 import { useContext } from 'react' import { ModalContext } from './ModalContext' const { open } = useContext(ModalContext) const showModal = () => { open({ title: '系统提示', content: '这是全局方法调用的弹窗' }) } ``` ### 关键实现原理 1. **响应式状态管理**:Vue使用`ref`实现响应式状态同步,React通过`useState`和上下文传递状态变化[^2] 2. **DOM传送技术**:Vue的`<teleport>`和React的`createPortal`都用于解决模态框层级问题[^3] 3. **全局访问**:Vue通过`globalProperties`扩展,React使用上下文提供全局访问[^1] ### 增强功能建议 1. 添加动画过渡效果 2. 实现拖拽功能(Vue使用`vuedraggable`,React使用`react-draggable`) 3. 支持Promise调用方式 4. 添加多种模态类型(确认框/提示框/加载中)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值