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

相关推荐
培根芝士40 分钟前
使用 Canvas 替代 <video> 标签加载并渲染视频
前端·javascript·音视频
小螺号dididi吹1 小时前
菜鸟速通:React入门 01
前端·react.js·前端框架
Lstmxx1 小时前
解放前端生产力:我如何用 LLM 和 Bun.js 构建一个 YApi to TypeScript 的自动化代码生成服务
前端·ai编程·mcp
持续前行1 小时前
vue3 : 导出pdf , 除iframe之外 ,还有其他内容一并导出方式
前端·javascript·vue.js
唐某人丶2 小时前
前端仔如何在公司搭建 AI Review 系统
前端·人工智能·aigc
没有鸡汤吃不下饭2 小时前
排查vue项目线上才会出现的故障
前端·vue.js·nginx
吃饭睡觉打豆豆嘛2 小时前
React Router 传参三板斧:新手也能 5 秒做决定
前端
裘乡2 小时前
storybook配合vite + react生成组件文档
前端·react.js
Carolinemy2 小时前
ElementUI 之 el-table
前端·vue.js
裘乡2 小时前
vonage音视频基本使用--web@opentok/client
前端·音视频开发