TypeScript-React 项目中的 Portals 使用指南
什么是 React Portals
Portals 是 React 提供的一种特殊功能,允许开发者将子节点渲染到存在于父组件 DOM 层次结构之外的 DOM 节点中。这在创建模态框、弹出窗口、工具提示等需要脱离常规 DOM 流的组件时特别有用。
为什么需要 Portals
在 React 应用中,组件通常按照其父组件-子组件的关系渲染到 DOM 中。然而,某些情况下我们需要将子组件渲染到 DOM 树的其他位置,例如:
- 模态对话框需要显示在其他内容之上
- 工具提示需要避免被父组件的 overflow:hidden 截断
- 全局通知需要独立于组件层级
Portals 完美解决了这些问题,同时保持了 React 的事件冒泡机制。
基础实现:类组件方式
在 TypeScript-React 项目中,我们可以这样实现一个 Portal 组件:
const modalRoot = document.getElementById("modal-root") as HTMLElement;
// 确保你的 HTML 文件中有一个 id 为 'modal-root' 的 div
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);
}
}
代码解析
- 首先我们获取目标 DOM 节点(modal-root)
- 在类组件中创建一个新的 div 元素作为 Portal 的容器
- 在组件挂载时将容器添加到 modal-root
- 在组件卸载时移除容器
- 使用 ReactDOM.createPortal 将子组件渲染到容器中
现代实现:函数组件与 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) {
// 使用 useRef 保持容器元素的引用
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);
}
代码解析
- 使用 useRef 来保持对容器元素的引用
- 在组件首次渲染时创建容器元素
- 使用 useEffect 处理挂载和卸载逻辑
- 返回通过 createPortal 创建的 Portal
使用示例
下面是一个完整的 Modal 组件使用示例:
import { useState } from "react";
function App() {
const [showModal, setShowModal] = useState(false);
return (
<div>
{/* 这个 div 也可以放在你的静态 HTML 文件中 */}
<div id="modal-root"></div>
{showModal && (
<Modal>
<div
style={{
display: "grid",
placeItems: "center",
height: "100vh",
width: "100vw",
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>
);
}
注意事项
-
事件冒泡:通过 Portal 渲染的元素仍然存在于 React 树中,因此事件会冒泡到 React 树中的祖先组件,而不是 DOM 树中的祖先。
-
生命周期:Portal 中的组件仍然遵循 React 组件的生命周期。
-
SSR:在服务器端渲染时需要注意 Portal 的使用,因为 document 对象在服务器端不可用。
-
性能:频繁创建和销毁 Portal 可能会影响性能,考虑复用 Portal 容器。
-
可访问性:确保 Portal 内容对屏幕阅读器等辅助技术友好。
最佳实践
- 为 Portal 容器添加适当的 ARIA 属性以提高可访问性
- 考虑使用 CSS 固定定位或绝对定位来确保 Portal 内容显示在正确位置
- 对于频繁使用的 Portal,考虑使用状态管理来全局控制
- 在 TypeScript 中明确定义 Portal 组件的 props 类型
通过掌握 Portals 的使用,你可以在 TypeScript-React 项目中创建更灵活、更强大的用户界面组件。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考