拓冰建站拓冰建站
首页 / 资讯中心 / 正文

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),仅供参考
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门