React + TypeScript:基于 react-typescript-cheatsheet 掌握 createPortal 的类型化实践

发布时间:2026/9/18 13:37:10

React + TypeScript:基于 react-typescript-cheatsheet 掌握 createPortal 的类型化实践 React TypeScript基于 react-typescript-cheatsheet 掌握 createPortal 的类型化实践【免费下载链接】reactCheatsheets for experienced React developers getting started with TypeScript项目地址: https://gitcode.com/gh_mirrors/reactt/react-typescript-cheatsheet本篇基于 react-typescript-cheatsheet 仓库中的 Portals 文档系统讲解如何用 TypeScript 编写基于createPortal的 React 弹窗组件。你将完整掌握类组件与函数组件Hooks两种实现中全部的类型断言、useRef泛型、ReactNode子属性标注等细节并获得一个可直接复制运行的 Modal 使用示例——这正是把组件内容渲染到 React 树之外 DOM 节点如挂载到#modal-root这一 React 核心机制在 TS 环境下的标准写法。为什么需要 Portal在 Modal 等弹层场景中弹窗 DOM 往往需要挂载到document.body下的独立容器如#modal-root以脱离父组件的overflow、transform、z-index上下文约束。ReactDOM.createPortal(children, container)就是把子树渲染到另一个 DOM 节点的标准 API。而 TypeScript 开发者要额外面对的问题只有三类容器元素可能是null——document.getElementById/document.querySelector的返回类型都带| null必须显式处理断言或判空动态创建的 DOM 节点的类型——document.createElement(div)返回HTMLDivElement作为类字段或 ref 存储时如何标注children的类型——透传任意可渲染内容时应使用React.ReactNode。Portals 文档给出的两套示例恰好完整覆盖了这三种情况以下逐一继承并展开。类组件实现完整的 Modal 组件原始文档给出的类组件版本摘自 portals.mdconst modalRoot document.getElementById(modal-root) as HTMLElement; // assuming in your html file has a div with id modal-root; 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); } }这段代码的每个类型细节都值得拆解as HTMLElement断言document.getElementById返回HTMLElement | null。示例假设 HTML 中已存在id为modal-root的div因此用类型断言剥离null分支。这是一种由调用方保证容器存在的写法若容器可能缺失更稳妥的做法是先判空再抛出明确错误。React.Component{ children?: React.ReactNode }泛型参数即 props 类型。children标注为React.ReactNode是因为透传组件应接受React 能渲染的一切。仓库的 ReactNode 参考文档给出了它的完整定义ReactElement、string、number、bigint、boolean、null、undefined、IterableReactNode、ReactPortal、PromiseReactNode的联合类型。注意其中明确包含ReactPortal——也就是说 Portal 本身就是合法的 children 内容嵌套 Portal 在类型层面是畅通的。el: HTMLElement document.createElement(div)类字段在实例化时执行一次创建一个游离的div。这里刻意标注为更宽的HTMLElement而非HTMLDivElement不影响 append/remove 操作。生命周期对应关系componentDidMount中把节点挂入modalRootcomponentWillUnmount中移除与函数组件中useEffect的注册 清理函数完全同构。render()返回 PortalcreatePortal(this.props.children, this.el)的第一个参数类型正是ReactNode第二个参数是Element | DocumentFragment所以游离节点this.el可以直接作为容器传入。Hooks 实现同一组件的函数式写法文档随后给出 Hooks 版本它把只创建一次 DOM 节点这一不变量迁移到了useRef上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) { // create div element only once using ref const elRef useRefHTMLDivElement | null(null); if (!elRef.current) elRef.current document.createElement(div); useEffect(() { const el elRef.current!; // non-null assertion because it will never be null modalRoot.appendChild(el); return () { modalRoot.removeChild(el); }; }, []); return createPortal(children, elRef.current); }对照类组件版本这里的类型处理有三个典型手法useRefHTMLDivElement | null(null)ref 泛型显式包含null因为初始值就是null。这与仓库中 hooks 相关的文档约定一致——Option 2: Mutable value ref式的可变值 ref。惰性初始化if (!elRef.current) elRef.current document.createElement(div)利用useRef的持久性保证 div 只创建一次等价于类组件的字段初始化。elRef.current!非空断言useEffect回调执行时elRef.current必然已被赋值上一行已保证但 TS 无法跨闭包追踪这一点因此用!断言。文档注释直接说明了理由non-null assertion because it will never be null。cleanup 函数即componentWillUnmountreturn () { modalRoot.removeChild(el); }是卸载时把节点从modalRoot摘除的唯一时机与类组件版本一一对应。另外注意两个版本的容器获取方式略有差异类组件用document.getElementByIdHooks 版用document.querySelector(#modal-root)两者返回类型同为可空类型所以都配合了as HTMLElement断言。组件使用示例带状态切换的 App文档还给出了一个完整的宿主应用示例展示 Modal 在真实页面中的挂载方式包括#modal-root容器本身也可以由 React 渲染import { useState } from react; function App() { const [showModal, setShowModal] useState(false); return ( div // you can also put this in your static html file div idmodal-root/div {showModal ( Modal div style{{ display: grid, placeItems: center, height: 100vh, width: 100vh, background: rgba(0,0,0,0.1), zIndex: 99, }} Im a modal!{ } button style{{ background: papayawhip }} onClick{() setShowModal(false)} close /button /div /Modal )} button onClick{() setShowModal(true)}show Modal/button // rest of your app /div ); }这里有两点值得展开#modal-root的位置灵活性示例把它放在 JSX 里注释也提示you can also put this in your static html file。两种放法类型上无差别区别只在加载时序——若 Modal 可能在静态容器之前渲染静态 HTML 中的div更稳妥。内联style的类型示例中的style对象由CSSProperties约束。仓库的 CSSProperties 参考文档说明它扩展自csstype的Propertiesstring | number因此display: grid、zIndex: 99这类键值都有自动补全与取值校验长度类属性传数字会被 React 自动追加pxheight: 100vh是字符串则原样透传。如果你要把这套弹窗样式抽成复用对象可以显式标注const card: CSSProperties { ... }让 TS 检查每个值。从Modal的 props 声明看children: ReactNode是必填项Hooks 版或children?: React.ReactNode类组件版可省略——两者都正确因为 children 本质上是 props 的一部分。事件冒泡为什么示例要强调Event Bubbling Through PortalPortals 文档 末尾注明该示例基于 React 官方文档的 Event Bubbling Through Portal 示例移植而来。这个细节对理解 Portal 至关重要DOM 树上Modal 的内容位于#modal-root内与App的其余 JSX 兄弟关系毫无关联React 组件树上Modal的 children 仍然从 JSX 声明位置向上冒泡。事件如onClick会像从未穿过 Portal 一样沿 React 树的父子链向上传播Modal的父组件可以正常e.stopPropagation()或处理事件只有focus 与 context不受此规则影响Portal 中的内容不会冒泡focus事件且Context仍然穿过 PortalProvider/Consumer 的对应关系按 React 树而非 DOM 树计算。因此上例中close按钮的onClick处理器虽然在 DOM 里执行于#modal-root之下但逻辑上仍归属于App组件树这是 Portal 心智模型中最容易踩坑的部分。文档在仓库中的组织与同步机制从源码结构看这份 Portals 文档并非孤立存在它被 website/sidebars.json 收录在 Learn 分类中位于forward_and_create_ref与error_boundaries之间说明仓库将 Portal 视为开始使用 React TS主学习路径上的标准一环仓库根目录的 README.md 中保留了完整的 Portals 章节README.md由!--START-SECTION:portals--到!--END-SECTION:portals--标记围合。这一同步由维护脚本完成根目录 package.json 的 scripts 中定义了gen-readme: node genReadme.mjs即 genReadme.mjs 会把docs/下的各文档按 front-matter 中的id抽取并写入 README 对应小节。这意味着阅读 docs/basic/getting-started/portals.md 即等同于阅读 README 中 Portals 章节的权威来源网站入口 website/src/pages/index.tsx 的描述文案中也把 portals 列入本 cheatsheet 覆盖的主题清单typing component props, hooks, class components, ... portals, error boundaries, concurrent rendering, and reusable patterns。小结掌握 Portal 的 TypeScript 写法核心就是三件事用as HTMLElement或判空处理可空的容器查询结果用useRefHTMLDivElement | null 惰性初始化 非空断言在 Hooks 中实现只创建一次的 DOM 节点用React.ReactNode标注透传的 children。类组件的componentDidMount/componentWillUnmount与useEffect注册/cleanup 两种生命周期写法在上述示例中是逐行对应的可按团队的技术栈选择其一。所有示例代码均可直接在 TypeScript Playground 中运行验证原文档为每个示例附带的 Playground 链接可参照 portals.md。【免费下载链接】reactCheatsheets for experienced React developers getting started with TypeScript项目地址: https://gitcode.com/gh_mirrors/reactt/react-typescript-cheatsheet创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
延伸阅读

更多相关文章

2026/9/18 13:37:10

开源代码智能代理OpenCode实战指南

我最初是从Codex那边摸过来的。当时在GitHub上看到一个名叫OpenCode的项目,标着“开源代码智能代理平台”,心想这不就是一个开源版的Claude Code或者Codex么?真正动手用了一个月之后,我发现自己已经离不开这个终端里的工具了——不…

2026/9/18 13:37:10

精馏课件制作指南:从相平衡到逐板计算动画演示

简介:面向化工及相关专业学生的《化工原理精馏》教学课件,系统梳理了精馏操作的基本原理、塔板数计算与回流比影响等核心知识。资源共1个文件,PPT格式,压缩包大小约1.01MB。课件从理想物系的气液平衡、拉乌尔定律、道尔顿分压定律…

2026/9/18 13:32:10

MySQL安装配置与忘记root密码重置、重装避坑指南

MySQL 这玩意儿,说它是后端开发的"水电煤"一点都不夸张。不管你是刚入行的新手,还是写了七八年 CRUD 的老手,几乎每隔一段时间就会跟它打一次交道——要么是新机器上装一套环境,要么是本地环境搞崩了需要卸载重装。而这…

2026/9/18 14:52:22

SecureCRT中文显示终极指南:UTF-8编码链路全打通

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/18 14:52:22

STM32 CubeMX嵌入式开发避坑指南:从配置到量产的工程实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/18 14:52:22

Win7开机启动项管理:注册表、服务与计划任务全指南

简介:这份 Word 文档系统梳理了 Windows 7 开机启动项的管理方法,面向需要优化系统启动速度、排查可疑自启动程序的普通用户,也适用于系统维护和技术支持人员。文档逐一说明启动文件夹在开始菜单和硬盘中的具体位置(C:\Documents …

2026/9/18 14:52:22

DH-DSS-H8900S2-B智慧园区平台配置实战:部署、APP开通与联动排错

简介:面向智慧园区综合管理平台的项目实施、系统集成和日常运维人员,这份帮助文档以 DH-DSS-H8900S2-B 平台的智慧园区APP为主要对象,系统讲解APP端各项业务的使用方法和平台侧配置流程。文档共收录一个DOCX文件,压缩包约7.18MB&a…

2026/9/18 14:52:22

单链表从原理到实现:数据结构核心操作与调试实战

链表这个东西,但凡你翻开任何一本数据结构教材,它基本都排在顺序表后面出场。我刚开始学的时候也没把它当回事,觉得数组用得好好的,凭空搞出一个"指针指来指去"的结构图啥。直到有一次写一个需要频繁在中间插入元素的程…

2026/9/18 14:13:01

拯救者Y7000黑屏故障排查与维修实战指南

1. 项目概述:一台黑屏的拯救者Y7000,到底卡在哪一步? 联想拯救者Y7000系列笔记本,从2018年第一代搭载i5-8300H开始,到后来的i7-9750H、i7-10750H、i5-11400H,再到2023年款的R7-7840HS,它始终是学…

2026/9/18 0:01:09

Google Colab 实战:运行模型、数据加载与报错排查

1. 为什么我劝你先搞懂 Colab 的运行模型1.1 Colab 到底是什么,跟本地跑代码差在哪Google Colab 简单说就是一台跑在浏览器里的 Linux 虚拟机,你打开一个 Notebook,背后就连上了一台带 GPU 的远程机器。你在单元格里敲的每一行 Python&#x…

2026/9/18 0:01:09

C语言数据类型与表达式详解

1. C语言数据与数据类型概述在C语言编程中,数据是程序处理的核心对象。理解数据的分类和特性是掌握C语言的基础。C语言中的数据主要分为四大类:常量、变量、表达式和函数。这些数据类型构成了C语言程序的基本元素,每种类型都有其独特的特性和…

2026/9/18 0:01:09

SQL时间字段指定时间段查询:区间语义、索引与时区避坑

上周排查一个线上问题&#xff0c;用户反馈"昨天的订单一条都没查到"&#xff0c;但数据库里明明躺着两千多条。最后定位下来&#xff0c;不是数据丢了&#xff0c;也不是接口挂了&#xff0c;而是那个查询条件把时间段写成了> 2024-05-20 00:00:00 AND < 2024…

2026/9/18 14:13:03

USB Type-C PCB布局分区设计:电源、高速信号与PD协议全攻略

做硬件这行&#xff0c;Type-C接口算是典型的“看着简单&#xff0c;做起来全坑”的东西。光引脚就24个&#xff0c;高低速信号、电源、控制线全部塞在一个小小的连接器里&#xff0c;如果PCB布局不做规划&#xff0c;打样回来基本就是“插上没反应”、“高速掉线”、“静电一打…

2026/9/18 14:13:02

系统编程学习原型如何补齐稳定性边界

系统编程学习原型如何补齐稳定性边界预算有限时&#xff0c;我先优化明显多余的复制&#xff0c;而不是猜测性地换容器。用借用传递只读数据通常就能减少分配&#xff1a; fn parse(line: &str) -> Result<Item, Error> { /* ... */ }用基准确认热点确实在分配&am…

2026/9/18 14:13:02

雨花区哪家财务公司代理记账比较好?

在雨花区&#xff0c;企业处理财税事务常常面临诸多挑战&#xff0c;选择一家靠谱的财务公司至关重要。湖南巨勤财务管理咨询有限公司就是本地正规实体财税服务机构&#xff0c;深耕本地工商财税行业多年&#xff0c;熟悉当地工商局、税务局最新政策与申报流程。主营公司注册、…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
咨询二维码