React 进阶:一个优雅的弹窗管理 Hook 实现

前言

在 React 项目中,弹窗管理一直是一个常见的需求。传统的弹窗管理方式往往需要在组件中维护多个 state,代码繁琐且不易维护。今天分享一个基于 Hook 的弹窗管理方案,让弹窗管理变得更加优雅。

实现目标

  1. 简化弹窗的打开和关闭操作
  2. 支持动态传递参数
  3. 自动销毁不需要的弹窗实例

核心实现

tsx 复制代码
import { useMemoizedFn } from "ahooks";
import React, { cloneElement, isValidElement, useRef, useState } from "react";

export type DialogProps = { onOpenChange: (open: boolean) => void; open: boolean,afterClose: ()=> void };

/**
 * 弹窗
 * @description 通过hook管理弹窗
 * @example
 * const [dialog, dialogContextHolder] = useDialog(Com)
 * 
 * dialog.show({})
 * dialog.close()
 * 
 * return (<>{dialogContextHolder}</>)
 */
export function useDialog<T = object>(dialogRender: any) {
  const [open, setOpen] = useState(false);
  const [destroyDialogRender,setDestroyDialogRender] = useState(true)
  const propsRef = useRef<T | null>(null);

  const onOpenChange = useMemoizedFn((open: boolean)=>{
    setOpen(open)
  })

  const afterClose = useMemoizedFn(() => {
    setDestroyDialogRender(true)
  })

  const show = useMemoizedFn((props?: T) => {
    propsRef.current = props ?? null;
    setDestroyDialogRender(false)
    onOpenChange(true);
  });
  const close = useMemoizedFn(() => {
    propsRef.current = null;
    onOpenChange(false);
  });

  const memoizedDialogRender = useMemoizedFn(() => {
    const props = {
      ...propsRef.current,
      open,
      onOpenChange,
      afterClose
    } as DialogProps & T;
    
    try {
      if (isValidElement(dialogRender)) {
        return cloneElement(dialogRender, props);
      }
      if (typeof dialogRender === "function") {
        return React.createElement(dialogRender, props);
      }
      console.error("dialogRender must be a ReactElement or a function");
      return null;
    } catch (error) {
      console.error("Dialog render failed:", error);
      return null;
    }
  });
  const dialogContextHolder = destroyDialogRender ? null :  memoizedDialogRender();
  return [
    {
      show,
      close,
      open,
    },
    dialogContextHolder,
  ];
}

核心方法解析

  1. 显示和关闭方法
tsx 复制代码
const show = useMemoizedFn((props: T) => {
  propsRef.current = props;
  setDestroyDialogRender(false);
  onOpenChange(true);
});

const close = useMemoizedFn(() => {
  propsRef.current = null;
  onOpenChange(false);
});
  1. 弹窗渲染逻辑
tsx 复制代码
const memoizedDialogRender = useMemoizedFn(() => {
  const props = {
    ...propsRef.current,
    open,
    onOpenChange,
    afterClose
  } as DialogProps & T;
  
  if (isValidElement(dialogRender)) {
    return cloneElement(dialogRender, props);
  }
  if (typeof dialogRender === "function") {
    return React.createElement(dialogRender, props);
  }
  return null;
});

使用示例

tsx 复制代码
import { useDialog,afterClose, type DialogProps } from './useDialog';
import React,{ FC } from 'react';
import { Button, Modal } from 'antd';

// 弹窗组件
const MyDialog:FC<{name: string} & DialogProps> = ({ open, onOpenChange,afterClose, name }) => (
  <Modal open={open} onCancel={() => onOpenChange(false)} afterClose={afterClose}>
    <div>Hello, {name}!</div>
  </Modal>
);

// 使用 Hook
const MyComponent = () => {
  const [dialog, dialogHolder] = useDialog<{name: string}>(MyDialog);

  const handleClick = () => {
    dialog.show({ name: 'React' });
  };

  return (
    <>
      <Button onClick={handleClick}>打开弹窗</Button>
      {dialogHolder}
    </>
  );
};

通过这个 Hook,我们可以用更优雅的方式管理弹窗状态。如果觉得文章有帮助,欢迎点赞转发!

相关推荐
Jing_Rainbow10 小时前
【 前端三剑客-37 /Lesson61(2025-12-09)】JavaScript 内存机制与执行原理详解🧠
前端·javascript·程序员
UIUV11 小时前
模块化CSS学习笔记:从作用域问题到实战解决方案
前端·javascript·react.js
aoi11 小时前
解决 Vue 2 大数据量表单首次交互卡顿 10s 的性能问题
前端·vue.js
Kakarotto11 小时前
使用ThreeJS绘制东方明珠塔模型
前端·javascript·vue.js
donecoding11 小时前
TypeScript `satisfies` 的核心价值:两个例子讲清楚
前端·javascript
德育处主任11 小时前
『NAS』在群晖部署一个文件加密工具-hat.sh
前端·算法·docker
cup11311 小时前
【原生 JS】支持加密的浏览器端 BYOK AI SDK,助力 Vibe Coding
前端
用户120391129472611 小时前
使用 Tailwind CSS 构建现代登录页面:从 Vite 配置到 React 交互细节
前端·javascript·react.js
杨进军11 小时前
模拟 Taro 实现编译多端样式文件
前端·taro
阿珊和她的猫12 小时前
React Hooks:革新组件开发的优势与实践
前端·react.js·状态模式