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,我们可以用更优雅的方式管理弹窗状态。如果觉得文章有帮助,欢迎点赞转发!

相关推荐
于慨2 天前
Lambda 表达式、方法引用(Method Reference)语法
java·前端·servlet
石小石Orz2 天前
油猴脚本实现生产环境加载本地qiankun子应用
前端·架构
从前慢丶2 天前
前端交互规范(Web 端)
前端
CHU7290352 天前
便捷约玩,沉浸推理:线上剧本杀APP功能版块设计详解
前端·小程序
GISer_Jing2 天前
Page-agent MCP结构
前端·人工智能
王霸天2 天前
💥别再抄网上的Scale缩放代码了!50行源码教你写一个永不翻车的大屏适配
前端·vue.js·数据可视化
小领航2 天前
用 Three.js + Vue 3 打造炫酷的 3D 行政地图可视化组件
前端·github
@大迁世界2 天前
2026年React大洗牌:React Hooks 将迎来重大升级
前端·javascript·react.js·前端框架·ecmascript
PieroPc2 天前
一个功能强大的 Web 端标签设计和打印工具,支持服务器端直接打印到局域网打印机。Fastapi + html
前端·html·fastapi
悟空瞎说2 天前
深入 Vue3 响应式:为什么有的要加.value,有的不用?从设计到源码彻底讲透
前端·vue.js