WHAT - React Portal 机制:将子组件渲染到 DOM 的指定节点

发布于:2025-04-12 ⋅ 阅读:(34) ⋅ 点赞:(0)

适合场景

React Portal 是 React 提供的一种机制,让你可以将子组件渲染到 DOM 的指定节点中,而不是默认的父组件 DOM 层级中。

非常适合:
✅ 弹窗(Modal)
✅ Tooltip
✅ Popover
✅ 全局消息提示(Toast)等浮层组件的实现

基本语法

import { createPortal } from 'react-dom';

const MyPortal = () => {
  return createPortal(
    <div>I’m rendered in a portal!</div>,
    document.getElementById('portal-root')! // 目标容器
  );
};

你需要确保 HTML 里有这个 DOM 节点:

<!-- index.html -->
<body>
  <div id="root"></div>
  <div id="portal-root"></div>
</body>

示例:Modal 弹窗

1. 创建一个简单的 Modal.tsx

import React from 'react';
import ReactDOM from 'react-dom';

type ModalProps = {
  visible: boolean;
  onClose: () => void;
  children: React.ReactNode;
};

const Modal: React.FC<ModalProps> = ({ visible, onClose, children }) => {
  if (!visible) return null;

  return ReactDOM.createPortal(
    <div style={styles.mask}>
      <div style={styles.modal}>
        {children}
        <button onClick={onClose}>Close</button>
      </div>
    </div>,
    document.getElementById('portal-root')!
  );
};

const styles = {
  mask: {
    position: 'fixed' as const,
    top: 0, left: 0, right: 0, bottom: 0,
    backgroundColor: 'rgba(0, 0, 0, 0.3)',
    display: 'flex',
    justifyContent: 'center',
    alignItems: 'center',
  },
  modal: {
    background: '#fff',
    padding: 20,
    borderRadius: 8,
    minWidth: 300,
  },
};

export default Modal;

2. 在 App 中使用

import React, { useState } from 'react';
import Modal from './Modal';

const App = () => {
  const [visible, setVisible] = useState(false);

  return (
    <>
      <button onClick={() => setVisible(true)}>Open Modal</button>
      <Modal visible={visible} onClose={() => setVisible(false)}>
        <h2>This is a Portal Modal</h2>
      </Modal>
    </>
  );
};

export default App;

为什么要用 Portal?

如果你直接把 Modal 放在组件里,它可能被某些 CSS 样式限制(比如 overflow: hiddenz-index),无法正确展示。

Portal 允许你将组件“挂”在 HTML 的任意节点(如根节点外层),避免被父组件样式影响,同时又保持了 React 的事件机制和数据流。

TypeScript 中 Portal 类型定义?

ReactDOM.createPortal 的类型如下:

createPortal(
  children: ReactNode,
  container: Element | DocumentFragment
): ReactPortal

所以你通常会这么写:

ReactDOM.createPortal(
  <div>内容</div>,
  document.getElementById('portal-root')!
);

其中返回值类型是 React.ReactPortal,它是 ReactNode 的子类型,意味着它可以被正常地渲染。