ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

enzyme ShallowWrapper 的 children() 方法:从获取直接子节点到按选择器过滤的完整实践指南

enzyme ShallowWrapper 的 children() 方法:从获取直接子节点到按选择器过滤的完整实践指南 enzyme ShallowWrapper 的 children() 方法从获取直接子节点到按选择器过滤的完整实践指南【免费下载链接】enzymeJavaScript Testing utilities for React项目地址: https://gitcode.com/gh_mirrors/en/enzyme导读.children([selector])是 enzyme 中 ShallowWrapper 提供的核心遍历方法用于返回当前 wrapper 中节点们的所有直接子节点并可选用EnzymeSelector选择器过滤结果。本文将围绕该方法的签名、返回语义、底层实现ShallowWrapper.js 与 RSTTraversal.js 中childrenOfNode以及共享测试套件children.jsx中的实证用例展开帮助你彻底掌握 shallow 渲染下的子节点遍历与断言技巧。.children([selector])方法签名与语义签名.children([selector]) ShallowWrapper参数selectorEnzymeSelector可选用于过滤子节点的选择器。enzyme 支持四类选择器——CSS 选择器、React 组件构造函数、组件displayName仅当首字母大写时、对象属性选择器详见下文。返回值ShallowWrapper一个包装了结果节点的新 wrapper 实例。从源码实现看ShallowWrapper.jschildren(selector) { const allChildren this.flatMap((n) childrenOfNode(n.getNodeInternal())); return selector ? allChildren.filter(selector) : allChildren; }其核心语义为对当前 wrapper 中的每个节点通过flatMap遍历取其直接子节点最终将所有子节点合并为一个扁平化且去重的新 wrapper若提供了selector则再对新 wrapper 应用.filter(selector)进行过滤与 ReactWrapper.children 的实现完全一致说明该方法在 shallow 与 mount 两种模式下语义统一。注意与.parent()/.parents()不同.children()返回的是直接子节点绝不包含孙节点或更深层后代因此它是「兄弟节点收集」与「按位置断言」的基石方法。深入底层childrenOfNode 如何定义「子节点」children方法的数据来源是 RSTTraversal.js 中的childrenOfNode(node)export function childrenOfNode(node) { if (!node) return []; const adapter getAdapter(); const adapterHasIsFragment adapter.isFragment typeof adapter.isFragment function; const renderedArray Array.isArray(node.rendered) ? flat(node.rendered, 1) : [node.rendered]; // React adapters before 16 will not have isFragment if (!adapterHasIsFragment) { return renderedArray; } return flat(renderedArray.map((currentChild) { // If the node is a Fragment, we want to return its children, not the fragment itself if (adapter.isFragment(currentChild)) { return childrenOfNode(currentChild); } return currentChild; }), 1); }这段实现揭示了几个关键细节空节点处理node为null/undefined时直接返回空数组这与测试中「无子节点的节点返回长度为 0 的 wrapper」一致children.jsx。数组扁平化React 渲染结果中的数组子节点如items.map(...)产生的数组会被flat(..., 1)拍平一层因此混合普通子节点与数组子节点时children()仍返回平铺的全部直接子节点。Fragment 穿透React 16当适配器实现了isFragmentReact 16 起时Fragment 节点本身不会出现在结果中而是被递归展开返回其内部子节点。这意味着 shallow 渲染下Fragment对children()的观察是透明的。适配器依赖是否展开 Fragment 取决于当前激活的 adapter通过getAdapter()获取这也解释了为何不同 React 版本适配器如 enzyme-adapter-react-16 等行为可能略有差异。四类 EnzymeSelector 过滤方式children(selector)的可选参数与find/filter使用同一套EnzymeSelector详见 selector.md共四种写法1. CSS 选择器支持类名.foo、.foo-bar、元素标签div、span、id#foo、属性选择器[hreffoo]、[typetext]等、通配符*以及组合选择器div.foo.bar、.foo .bar、.foo .bar等。属性匹配支持按值类型区分——[anum3]匹配数字 3而[anum3]才匹配字符串3布尔值同理。2. React 组件构造函数传入组件构造函数引用即可匹配对应组件类型仅比较类型忽略 props 与 childrenfunction MyComponent() { return div /; } const myComponents wrapper.children(MyComponent);3. displayName 字符串当组件设置了首字母大写的displayName时可以用字符串匹配注意以小写字母开头的字符串会被当作 CSS 标签选择器。4. 对象属性选择器按 props 子集匹配{ foo: 3 }、{ bar: false }、{ title: baz }但undefined值会抛出TypeError此时应改用.findWhere()。实战示例从根节点获取并过滤子节点原文档给出了一个最典型的用法——从组件根节点获取列表项子节点并断言数量const wrapper shallow(ToDoList items{items} /); expect(wrapper.find(ul).children()).to.have.lengthOf(items.length);将示例拆解为完整可运行的测试流程import { shallow } from enzyme; import { expect } from chai; function ToDoList({ items }) { return ( ul {items.map((item) li key{item.id}{item.text}/li)} /ul ); } const items [ { id: 1, text: foo }, { id: 2, text: bar }, { id: 3, text: baz }, ]; const wrapper shallow(ToDoList items{items} /); // 1. 先 find 到 ul 根节点再取全部直接子节点 const listItems wrapper.find(ul).children(); expect(listItems).to.have.lengthOf(items.length); // 3 // 2. 结合 .at() 按索引断言 expect(listItems.at(0).hasClass()).to.equal(true); expect(listItems.at(1).text()).to.equal(bar); // 3. 提供选择器过滤 const foo wrapper.find(ul).children(.done); expect(foo).to.have.lengthOf(0);这里需要注意两点children()只取find(ul)的直接子节点li内部的文本或嵌套元素不会出现在结果中返回值是新的 ShallowWrapper因此可以继续链式调用.at()、.hasClass()、.text()、.map()、.filter()等方法。共享测试套件中的行为细则enzyme-test-suite 中的describeChildren测试函数同时被 ShallowWrapper 与 ReactWrapper 的 spec 复用完整覆盖了该方法的所有边界行为空节点与文本节点无子节点的节点返回长度为 0 的 wrapperL15-L18文本节点被包含在结果中divBspan /C/div的 children 长度为 3B、span /、CL20-L23文本节点没有 React 实例shallow 模式下对文本节点调用instance()会失败L25-L28。跳过 falsy 子节点false、null、undefined以及包含这些值的数组都会被过滤掉只保留真实渲染的节点L30-L53。只返回直接子节点嵌套结构div classNamefoodiv classNamebar //divdiv classNamebaz /的children()长度为 2孙节点bar不会出现L69-L81。混合数组子节点普通子节点与items.map(...)生成的数组子节点混排时结果仍按渲染顺序平铺为完整列表L83-L107这也对应源码中flat(node.rendered, 1)的拍平逻辑。选择器过滤对div classNamefoo /、div classNamebar bip /、div classNamebaz bip /调用.children(.bip)返回 2 个节点且保持原顺序L109-L121。重复文本与空格相同文本节点会原样保留、不去重Foo出现三次L147-L165通过React.Children.map/forEach生成并用 分隔的子节点空格本身也作为文本节点出现在结果中L167-L215断言时可用.text()或.debug()验证。无状态函数组件SFCReact 0.13 之后SFC 渲染出的混合子节点同样能正确处理L123-L145。常见误区与最佳实践children() 不是 find()find会深度遍历整个渲染树而children只取直接子节点。需要收集某节点下所有后代时用find需要逐个断言直接子节点时用children。不要对文本节点调用组件方法文本节点无实例应使用.text()断言内容。falsy 子节点已自动过滤无需在断言前手动清理false/null/undefined。Fragment 是透明的React 16 适配器下children()不会返回 Fragment 节点本身而是其内部子节点。链式组合children()通常与.at()、.first()、.last()、.map()配合实现按索引、按顺序的细粒度断言。关联方法速览children()与同族遍历方法配合使用可覆盖绝大多数结构断言场景.parents([selector])当前 wrapper 中节点的所有祖先不含自身.parent()当前 wrapper 中单个节点的直接父节点.closest(selector)从当前节点向上查找第一个匹配选择器的祖先.childAt(index)基于children()实现的按索引取单个子节点源码见 ShallowWrapper.js。如需进一步了解选择器语法细节请查阅 enzyme Selectors想对比 mount 模式下的行为可参考 ReactWrapper.children。【免费下载链接】enzymeJavaScript Testing utilities for React项目地址: https://gitcode.com/gh_mirrors/en/enzyme创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进