30-seconds-of-code 实战:使用 Jest 正确测试 React Portal 组件

发布时间:2026/10/6 2:09:59
30-seconds-of-code 实战:使用 Jest 正确测试 React Portal 组件 教程文档【免费下载链接】30-seconds-of-codeCoding articles to level up your development skills项目地址https://gitcode.com/gh_mirrors/30/30-seconds-of-code点击查看免费下载在 React 开发中测试使用ReactDOM.createPortal()的组件往往令许多开发者头疼Portal 将子树渲染到父组件之外的 DOM 节点导致测试环境与真实应用环境的 DOM 结构不一致。本文以 30-seconds-of-code 仓库中的 Testing React portals 一文为核心结合仓库内 usePortal Hook、异步组件测试 与 有状态组件测试 等兄弟篇章系统讲解 Portal 测试的正确思路、基于 Jest 的可运行 mock 方案及其边界限制帮助你写出稳健、可维护的 Portal 测试用例。理解 Portal渲染位置的变化而非渲染逻辑的变化在讨论测试之前先厘清 Portal 的本质。React 官方将 Portal 描述为把子节点渲染到父组件以外的 DOM 节点的能力常用场景包括模态框、通知、悬浮提示等需要脱离组件层级树、直接挂载到document.body或其他容器元素的 UI。仓库中的 usePortal Hook 给出了一个非常直观的程序化实现它用useState()保存render()与remove()两个函数通过useCallback()把ReactDOM.createPortal(children, el)封装成可复用的Portal组件再用useEffect()在el变化时重建 portal 并返回render()。这段代码清晰地表明Portal 本身只是换个地方渲染其内部子组件依旧遵循 React 正常的渲染与更新机制任何经由createPortal()输出的 JSX 内容本质上仍然是一个普通的 React 渲染输出。这一点对测试至关重要我们真正关心的从来不是 Portal 被挂到了哪个节点而是被portal 化之后渲染出来的输出portalized output是否正确。渲染位置属于 React 框架内部的调度行为而输出的 JSX 树才是我们业务代码的职责所在。测试原则永远不要测试框架内部原文强调了一个在 30-seconds-of-code 多篇测试文章中反复出现的核心原则编写测试时应当避免测试框架自身的内部实现。这条原则显然同样适用于 React 自身。这一理念在仓库的 有状态组件测试 中体现得尤为充分该文提出应用程序的 UI 始终是 props 与 state 组合的结果因此主张把组件 state 当作黑盒断言组件是否渲染出expandedclass而不是断言wrapper.state(expanded)的布尔值。推及 Portal 场景等价的做法就是断言被渲染的输出而不是断言 React 把输出挂载到了哪个 DOM 节点、走了什么调度流程。把这两点合并就能得到 Portal 测试的正确策略验证 Portal 渲染输出的正确性而不去模拟或验证createPortal()底层的挂载行为。核心方案用 Jest mock 掉ReactDOM.createPortal()既然我们只关心 portal 化输出mock 起来就不应该困难——只需把ReactDOM.createPortal()替换为一个原地返回元素的实现即可。原文档给出了完整的 Jest 测试骨架这里逐段拆解describe(MyComponent, () { beforeAll(() { ReactDOM.createPortal jest.fn((element, node) { return element; }); }); afterEach(() { ReactDOM.createPortal.mockClear(); }); it(should render correctly, () { const component renderer.create(MyComponentHello World!/MyComponent); expect(component.toJSON()).toMatchSnapshot(); }); });beforeAll阶段替换实现ReactDOM.createPortal jest.fn((element, node) element)让createPortal()直接返回传入的element本身。这样Portal 包裹的子树在测试渲染器中原地出现参与正常的快照与断言测试不再关心真实挂载节点nodeafterEach阶段清理 mock 调用记录ReactDOM.createPortal.mockClear()清空上一次用例的调用统计保证每个用例之间互不污染快照断言renderer.create()来自react-test-renderer配合toJSON()与toMatchSnapshot()验证组件输出的完整结构。需要说明的是示例中renderer.create(...)是react-test-renderer的用法如果项目采用 React Testing Library同样可以保留上述 mock用render()配合screen/container查询并断言 portal 化输出是否出现在预期位置mock 思路完全一致。这种 mock 之所以在大多数情况下都能工作正是因为它精准地绕开了框架内部、只保留了业务输出。它让快照完整反映被 Portal 包裹的内容确保后续对组件内部 JSX 的改动会被测试及时发现。方案的局限两份 DOM 与缺失节点问题原文档明确提醒上述 mock 方案并非银弹它伴随两个必须正视的注意事项。局限一测试 DOM 与真实应用 DOM 不同mock 后createPortal()不再把子树挂载到真实的目标容器而是渲染在测试渲染器的当前树中。这意味着测试中看到的 DOM 结构与应用运行时实际的 DOM 结构存在差异依赖 DOM 层级关系的查询例如查找某个节点是否位于document.body之下会得到与生产环境不一致的结果测试对Portal 在实际 DOM 中的表现的置信度降低可能在测试通过但真实场景出现问题时形成误判。局限二Portal 特有的问题必须单独测试Mock 屏蔽了真实挂载也就同时屏蔽了与挂载相关的故障场景。例如portal 目标节点缺失node为null或尚未渲染到 DOM这类真实环境中的高频问题在本方案下根本不会暴露。原文档明确要求这类问题必须单独编写测试覆盖不能指望上面的 mock 顺带兜底。针对缺失节点的验证可以从 usePortal Hook 的实现中获得启示该 Hook 的useEffect会在el变化时执行portal.remove()再重建说明真实业务中 portal 的目标节点是动态的、可变的。相应地测试应当覆盖目标节点存在时正常渲染与目标节点缺失时行为符合预期如不崩溃、渲染兜底内容两条路径例如断言it(renders fallback content when the portal node is missing, () { // 将目标节点从 DOM 中移除后渲染组件 // 断言输出中只包含兜底内容且不抛出异常 });与仓库其他测试实践的呼应本篇文章是 30-seconds-of-code React 测试系列的一员。从 react 集合配置 可见该集合面向 React 18 收录了大量函数组件与可复用 Hook 文章测试主题下还包含另外两篇与本文思想同源的实践异步组件更新测试解决拖拽等异步更新场景中act(...)警告与MutationObserver错误主张用waitFor()等待真实 UI 状态就绪后再断言有状态组件测试主张把 state 视为黑盒通过 DOM 输出断言组件行为。三篇文章共同构成一条连贯的方法论无论组件是否使用 Portal、是否包含异步更新、是否维护内部状态测试的锚点都应落在用户可见的输出上而不是框架或实现细节上。本文提供的createPortal()mock 正是这一方法论在 Portal 场景下的直接落地。小结React Portal 的实质是在 DOM 中换个位置渲染业务输出本身仍是普通 React 渲染结果因此测试应聚焦于 portal 化输出是否正确通过beforeAll中把ReactDOM.createPortalmock 为原地返回元素即可在 Jest react-test-renderer 中方便地完成快照测试实现简单、在多数场景下有效该方案无法覆盖真实 DOM 层级差异也无法捕获 portal 目标节点缺失等问题后者必须设计独立的专项测试测试时应坚持不测试框架内部原则始终以组件输出而非框架行为作为断言对象这与仓库内其他 React 测试文章异步组件、有状态组件的方法论保持一致。赞分享教程文档【免费下载链接】30-seconds-of-codeCoding articles to level up your development skills项目地址https://gitcode.com/gh_mirrors/30/30-seconds-of-code点击查看免费下载相关推荐30 seconds of code用 Array.isArray() 正确进行 JavaScript 数组类型检查30 seconds of code用 Array.isArray 正确进行 JavaScript 数组类型检查 判断一个值是不是数组是 JavaScrip教程文档30 Seconds of Code正确检测 JavaScript 对象中 undefined 属性30 Seconds of Code正确检测 JavaScript 对象中 undefined 属性 在 JavaScript 中属性值为 undefin教程文档30 seconds of code 精讲使用 AbortController 正确取消 JavaScript fetch 请求30 seconds of code 精讲使用 AbortController 正确取消 JavaScript fetch 请求 Fetch API 如今已成教程文档上一篇终极指南如何将SAM2 Hiera Tiny视觉模型集成到现有计算机视觉项目中下一篇QtScrcpy终极指南免费实现安卓手机投屏与多设备控制创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询