` 方法详解:严格比对 React 元素在挂载渲染树中的存在性)
Enzyme ReactWrapper.contains()方法详解严格比对 React 元素在挂载渲染树中的存在性【免费下载链接】enzymeJavaScript Testing utilities for React项目地址: https://gitcode.com/gh_mirrors/en/enzyme导读.contains(nodeOrNodes)是 Enzyme 为ReactWrapper即mount()挂载后的包装器提供的元素存在性断言方法它返回一个布尔值表示传入的一个或多个 React 元素是否以完全一致的形式出现在当前包装器的整棵渲染树render tree中。与基于 CSS 选择器的.find()不同.contains()要求你传入的是 ReactElement 或 JSX 表达式并按照props 全部相同、children 完全相等的严格规则进行比对。阅读本文后你将掌握.contains()的完整用法、与.containsMatchingElement()等宽松匹配方法的核心区别以及其底层基于 RST 节点树的比对原理。方法签名与返回值.contains(nodeOrNodes) Boolean.contains()返回布尔值只要当前包装器的渲染树中存在一个节点与传入的节点或数组中全部节点完全相等就返回true否则返回false。参数说明参数类型说明nodeOrNodesReactElement或ArrayReactElement要检测是否存在于当前实例渲染树中的节点或节点数组注意参数是 ReactElement或 ReactElement 数组不是选择器selector。这一点与 Enzyme 中大量接受 CSS 选择器的方法如.find()、.filter()不同使用 JSX 表达式即可传入。返回值Boolean当前包装器渲染树中的任意位置是否存在与传入节点匹配的节点。核心用法单个元素的严格比对先看一个最基础的例子它来自 contains.md 文档 中的原始示例const wrapper mount(( div div>const wrapper mount(( div div spanHello/span /div /div )); expect(wrapper.contains(spanHello/span)).to.equal(true);这一行为在共享测试套件 contains.jsx 中有专门用例覆盖allows matches on the root node 与 allows matches on a nested node 分别验证了根节点与深层节点的匹配场景。数组用法一次检测多个元素当传入的是 ReactElement 数组时.contains()要求数组中全部元素都出现在渲染树中允许每个元素出现多次数组中元素彼此独立匹配。const wrapper mount(( div spanHello/span divGoodbye/div spanAgain/span /div )); // 两个元素都存在 → true expect(wrapper.contains([ spanHello/span, divGoodbye/div, ])).to.equal(true); // 第二个元素不存在于渲染树 → false expect(wrapper.contains([ spanHello/span, divWorld/div, ])).to.equal(false);从测试套件中的 does something with arrays of nodes 用例可以看到数组匹配不要求顺序一致[divGoodbye/div, spanMore/span]顺序与渲染树相反同样返回true。支持的节点类型字符串与数字虽然文档签名写的是ReactElement | ArrayReactElement但从源码实现与测试用例看.contains()实际上也支持字符串和数字这类文本节点。共享测试套件 contains.jsx 中的用例证实// 字符串 const wrapper Wrap(divfoo/div); expect(wrapper.contains(foo)).to.equal(true); expect(wrapper.contains(bar)).to.equal(false); // 数字注意 1 ! 1类型不同不匹配 const wrapper2 Wrap(div{1}/div); expect(wrapper2.contains(1)).to.equal(true); expect(wrapper2.contains(2)).to.equal(false); expect(wrapper2.contains(1)).to.equal(false);此外在嵌套层级中的字符串与数字也能被正确检测到。同时传入普通对象或函数作为参数时会抛出如下错误该用例在非 mount 场景下验证ReactWrapper::contains() can only be called with a ReactElement (or an array of them), a string, or a number as an argument.复合组件与无状态函数组件的匹配.contains()不仅能匹配原生 DOM 节点也能匹配自定义复合组件。测试套件中 matches composite components 与 SFC 相关用例验证class Foo extends React.Component { render() { return div /; } } const wrapper Wrap(( div Foo / /div )); // 以 Foo / 元素作为参数进行匹配 const b Foo /; expect(wrapper.contains(b)).to.equal(true);函数组件SFC同样适用包括由普通函数调用渲染出来的组件这一边界情况。这意味着.contains()的比对发生在 React 元素层面只要元素类型type引用相同、props 与 children 相等就能匹配与渲染产物无关。底层实现从 ReactElement 到 RST 节点的严格相等比较理解了用法之后我们来剖析.contains()的实现。它定义在 ReactWrapper.jscontains(nodeOrNodes) { const adapter getAdapter(this[OPTIONS]); const predicate Array.isArray(nodeOrNodes) ? (other) containsChildrenSubArray( nodeEqual, other, nodeOrNodes.map((node) adapter.elementToNode(node)), ) : (other) nodeEqual(adapter.elementToNode(nodeOrNodes), other); return findWhereUnwrapped(this, predicate).length 0; }整个流程分为四步适配器转换通过当前 adapter如 React 16 的 ReactSixteenAdapter.js的elementToNode()把传入的 ReactElement 转成 enzyme 内部的RSTReact Shallow Test节点树。构造谓词如果传入数组则用containsChildrenSubArray(nodeEqual, ...)判断渲染树的某个节点的 children 中是否包含与目标数组逐位相等的连续子序列如果传入单个元素则直接使用nodeEqual判断渲染树中的每个节点与目标节点是否相等。全树搜索findWhereUnwrapped借助treeFilter定义于 RSTTraversal.js对包装器内所有节点的渲染树做深度优先遍历找出所有满足谓词的节点。结果判定只要找到的节点数量大于 0就返回true。nodeEqual的严格相等规则核心的相等判定函数是nodeEqual定义在 Utils.js其内部委托给internalNodeCompare。从 Utils.js 的实现可以归纳出以下规则节点类型必须相同a.type ! b.type直接返回falseprops 严格比对以左侧节点被检查的渲染树节点的 props 为基准要求每个属性都能在右侧节点传入的目标节点中找到且值相等——因此目标节点多出的属性会让比对失败这正是文档示例中data-bazbaz返回false的原因而缺失属性同样失败对象类型的 prop 值使用lodash.isequal进行深度相等比较children 递归比对children 会先经过childrenToSimplifiedArray处理把null、false、undefined过滤掉并合并相邻的文本节点再逐位比较文本节点同样参与严格比较非文本节点还要比较属性数量lenComp(leftKeys.length, rightKeys.length)默认的lenComp是object-is即要求属性个数也完全一致。这从源码层面解释了文档中缺失 prop 失败、多余 prop 失败、children 不同失败的全部行为。数组匹配的底层逻辑containsChildrenSubArray定义在 Utils.jsexport function containsChildrenSubArray(match, node, subArray) { const children childrenOfNode(node); const checker (_, i) arraysEqual(match, children.slice(i, i subArray.length), subArray); return children.some(checker); }它取出渲染树中每个节点的 children 数组通过滑动窗口检查是否存在一个与目标数组长度相同、逐位nodeEqual相等的连续子序列。注意数组元素匹配不要求覆盖全部 children——只要存在一个连续片段与目标数组完全对应即可由于是连续子序列匹配数组中元素的顺序必须与渲染树中的顺序一致这与测试用例[divGoodbye/div, spanMore/span]按原顺序匹配成功的场景吻合。常见陷阱Common Gotchas参数必须是 ReactElement不是选择器.contains()与.find()、.filter()等接受 CSS 选择器的方法不同必须传入 JSX 表达式或 ReactElement否则无法匹配。子节点也参与相等性判断即使 props 全部一致children包括文本不同也会导致返回false。文档中div>const wrapper mount(( div div contenteditable="false">【免费下载链接】enzymeJavaScript Testing utilities for React项目地址: https://gitcode.com/gh_mirrors/en/enzyme创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考