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

相关推荐
桂月二二7 分钟前
Vue3服务端渲染深度实战:SSR架构优化与企业级应用
前端·vue.js·架构
萌萌哒草头将军8 分钟前
🚀🚀🚀 这六个事半功倍的 Pinia 库,你一定要知道!
前端·javascript·vue.js
_一条咸鱼_8 分钟前
深入剖析 Vue 状态管理模块原理(七)
前端·javascript·面试
rocky19119 分钟前
谷歌浏览器插件 录制动态 DOM 元素
前端
谁还不是一个打工人22 分钟前
css解决边框四个角有颜色
前端·css
海晨忆1 小时前
【Vue】v-if和v-show的区别
前端·javascript·vue.js·v-show·v-if
JiangJiang2 小时前
🚀 Vue人看React useRef:它不只是替代 ref
javascript·react.js·面试
1024小神2 小时前
在GitHub action中使用添加项目中配置文件的值为环境变量
前端·javascript
齐尹秦2 小时前
CSS 列表样式学习笔记
前端
Mnxj2 小时前
渐变边框设计
前端