React 命令式组件:Modal

开始

在 React 开发中,我们的固有思路是数据驱动视图的,但是对于 Toast、Modal 等使用方式更加灵活的组件,或许可以搭配 DOM 操作来添加 Modal.open、Toast.info 等功能。

文末有 已成功帮助500多人拿到前端offer的文章 !!

实现

为了实现类似 antd 中既支持 jsx 语法,又支持 Modal.open 的 Modal 组件,除了常规的 React Component 代码,我还使用了 reactDom 中的 createRoot 方法,在调用 Modal.open 时动态添加一个 React 根节点来渲染 Modal 组件。

以下是代码实现,样式部分使用了 tailwindcss

import { useEffect, useRef, useState } from 'react';
import { createRoot, Root } from 'react-dom/client';

interface ModalContentProps {
  title?: string;
  closable?: boolean;
  children?: React.ReactNode;
  onClose?: () => void;
}

interface ModalProps extends ModalContentProps {
  visible?: boolean;
}

export default function Modal(props: ModalProps) {
  const [visible, setVisible] = useState(props.visible);
  const withCmd = isOpenWithCmd();

  const close = () => {
    if (withCmd) Modal.cancel();
    else setVisible(false);
    props.onClose?.();
  };

  const onMaskClick = (e: React.MouseEvent) => {
    if (props.closable !== false) {
      const target = e.target as HTMLElement;
      if (target.getAttribute('data-type')?.includes('modal-wrapper')) close();
    }
  };

  useEffect(() => {
    if (!withCmd) {
      if (visible && !props.visible) setVisible(false);
      else if (!visible && props.visible) setVisible(true);
    }
  }, [props.visible]);

  return (
    <div
      className={`fixed left-0 top-0 z-10 flex h-screen w-screen items-center justify-center bg-black bg-opacity-60 ${!visible && !withCmd ? 'hidden' : ''}`}
      data-type='modal-wrapper'
      onClick={onMaskClick}
    >
      <div className='min-w-80 rounded-md bg-white'>
        {props.title ? <div className='border-b p-4 text-xl font-bold'>{props.title}</div> : null}
        {props.children ? <div className='p-2 text-lg'>{props.children}</div> : null}
      </div>
    </div>
  );
}

let container: HTMLDivElement | null = null;
let root: Root | null = null;

Modal.open = (props: ModalContentProps) => {
  container = document.createElement('div');
  container.setAttribute('data-type', 'modal-root');
  root = createRoot(container);
  root.render(<Modal {...props} />);
  document.body.appendChild(container);
};

Modal.cancel = () => {
  if (container && root) {
    root.unmount();
    document.body.removeChild(container);
    container = root = null;
  }
};

/**
 * 通过 command 打开,会比通过 state 多一层容器,可以借此判断打开方式
 */
const isOpenWithCmd = () => !!document.body.querySelector('[data-type="modal-root"]');

总结

我通过 createRoot 的能力,为 Modal 组件添加了命令式控制的能力,让其可以摆脱 jsx 和 state 的限制,在使用方式上更加灵活,提高了简单场景下的开发效率。

同理,Toast、Message、Notification 等组件也可以通过这种方式实现。

程序员玫玫:堪称2024最强的前端面试场景题,已帮助512人成功拿到offer

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值