TypeScript-React 项目实战:深入理解 React Portals 机制

TypeScript-React 项目实战:深入理解 React Portals 机制

react react 项目地址: https://gitcode.com/gh_mirrors/reactt/react-typescript-cheatsheet

什么是 React Portals

React Portals 是 React 提供的一种特殊机制,允许开发者将子节点渲染到存在于父组件 DOM 层次结构之外的 DOM 节点中。这在处理模态框(Modal)、工具提示(Tooltip)或通知等需要"跳出"当前组件层级的情况下特别有用。

为什么需要 Portals

在 React 应用中,组件的渲染通常遵循父子组件的 DOM 结构。但有时我们需要将某些 UI 元素渲染到 DOM 树的其他位置,同时保持它们在 React 组件树中的逻辑位置。Portals 完美解决了这个问题,它提供了:

  1. 视觉上的"跳出"能力
  2. 保持事件冒泡机制
  3. 维持 React 组件树的上下文关系

基础实现:类组件方式

让我们先看一个使用类组件实现的 Portal 示例:

const modalRoot = document.getElementById("modal-root") as HTMLElement;

export class Modal extends React.Component<{ children?: React.ReactNode }> {
  el: HTMLElement = document.createElement("div");

  componentDidMount() {
    modalRoot.appendChild(this.el);
  }

  componentWillUnmount() {
    modalRoot.removeChild(this.el);
  }

  render() {
    return ReactDOM.createPortal(this.props.children, this.el);
  }
}

代码解析

  1. modalRoot:这是我们在 HTML 中预先定义的一个容器元素,通常放在 <body> 的末尾
  2. el:Modal 组件内部创建的 div 元素,将作为 Portal 的挂载点
  3. 生命周期方法:在组件挂载/卸载时管理 DOM 元素的添加/移除
  4. createPortal:React 提供的 API,将 children 渲染到指定的 DOM 节点

现代实现:函数组件与 Hooks

随着 React Hooks 的普及,我们可以用更简洁的方式实现相同的功能:

import { useEffect, useRef, ReactNode } from "react";
import { createPortal } from "react-dom";

const modalRoot = document.querySelector("#modal-root") as HTMLElement;

type ModalProps = {
  children: ReactNode;
};

function Modal({ children }: ModalProps) {
  const elRef = useRef<HTMLDivElement | null>(null);
  if (!elRef.current) elRef.current = document.createElement("div");

  useEffect(() => {
    const el = elRef.current!;
    modalRoot.appendChild(el);
    return () => {
      modalRoot.removeChild(el);
    };
  }, []);

  return createPortal(children, elRef.current);
}

代码改进点

  1. useRef:替代类组件中的实例属性,确保 DOM 元素只创建一次
  2. useEffect:处理副作用逻辑,返回的清理函数相当于 componentWillUnmount
  3. 类型定义:明确 ModalProps 的类型,提高代码可维护性
  4. 非空断言:使用 ! 操作符告诉 TypeScript 我们知道 elRef.current 不会为 null

实际应用示例

下面是一个完整的 Modal 组件使用示例:

import { useState } from "react";

function App() {
  const [showModal, setShowModal] = useState(false);

  return (
    <div>
      <div id="modal-root"></div>
      {showModal && (
        <Modal>
          <div
            style={{
              display: "grid",
              placeItems: "center",
              height: "100vh",
              width: "100vh",
              background: "rgba(0,0,0,0.1)",
              zIndex: 99,
            }}
          >
            I'm a modal!{" "}
            <button
              style={{ background: "papyawhip" }}
              onClick={() => setShowModal(false)}
            >
              close
            </button>
          </div>
        </Modal>
      )}
      <button onClick={() => setShowModal(true)}>show Modal</button>
    </div>
  );
}

样式处理技巧

  1. 全屏覆盖:使用 100vh100vw 确保 Modal 覆盖整个视口
  2. 半透明背景rgba(0,0,0,0.1) 创建半透明遮罩效果
  3. 层级控制:通过 z-index 确保 Modal 显示在其他内容之上
  4. 居中显示place-items: center 轻松实现内容居中

高级主题:事件冒泡机制

虽然 Portal 的内容被渲染到 DOM 树的不同位置,但它在 React 组件树中的位置保持不变。这意味着:

  1. 从 Portal 内部触发的事件会冒泡到包含 Portal 的 React 组件
  2. Context 能够正常工作,就像子节点是实际渲染位置的父节点一样
  3. 这保持了 React 的声明式特性,同时提供了 DOM 操作的灵活性

最佳实践

  1. 容器管理:确保 Portal 的目标容器在 DOM 中存在且唯一
  2. 清理工作:组件卸载时务必移除创建的 DOM 元素,避免内存泄漏
  3. 类型安全:为 Portal 组件定义清晰的 Props 类型,特别是 children 的类型
  4. 性能优化:避免在 Portal 中渲染大量内容,考虑使用 React.memo 优化

总结

React Portals 是处理需要"跳出"当前组件层级 UI 元素的强大工具。通过 TypeScript 的强类型检查,我们可以构建出更健壮、更易维护的 Portal 组件。无论是类组件还是函数组件,理解其核心原理和实现方式都能帮助我们在实际项目中更好地应用这一特性。

react react 项目地址: https://gitcode.com/gh_mirrors/reactt/react-typescript-cheatsheet

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

廉林俏Industrious

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值