news 2026/9/20 17:58:24

enzyme ReactWrapper.filterWhere 方法详解:基于谓词函数的节点过滤

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
enzyme ReactWrapper.filterWhere 方法详解:基于谓词函数的节点过滤

enzyme ReactWrapper.filterWhere 方法详解:基于谓词函数的节点过滤

【免费下载链接】enzymeJavaScript Testing utilities for React项目地址: https://gitcode.com/gh_mirrors/en/enzyme

.filterWhere(predicate)是 enzyme 中用于按自定义条件过滤 wrapper 内节点的核心方法,它允许开发者绕过选择器语法的限制,通过一个返回布尔值的谓词函数(predicate)对当前 wrapper 中的每个节点进行精细筛选,并返回一个只包含符合条件节点的新 wrapper。在 React 组件测试中,它常用于"只保留复合组件节点""剔除宿主 DOM 节点"等选择器难以直接表达的场景,也是 enzyme 内部实现filternothostNodes等方法的底层基础设施。读完本文,你将掌握filterWhere的完整签名、谓词函数的调用约定、底层实现原理,以及在实际测试中组合使用它的实战技巧。

方法签名与返回值

在 enzyme 中,ReactWrapper(由mount()产生)和ShallowWrapper(由shallow()产生)都提供了同名方法filterWhere,两者的语义与用法完全一致,仅返回的 wrapper 类型不同:

// ReactWrapper 版本 .filterWhere(predicate) => ReactWrapper // ShallowWrapper 版本 .filterWhere(predicate) => ShallowWrapper
  • 参数predicateReactWrapper => BooleanShallowWrapper => Boolean)——一个接收包装节点(wrapped node)的谓词函数,返回true表示保留该节点。
  • 返回值:一个新的 wrapper,只包含当前 wrapper 中通过谓词函数测试的节点。原 wrapper 不会被修改,这与 enzyme 大部分查询方法"返回新 wrapper、保持不可变"的设计一致。

官方文档对它的定义是:"Returns a new wrapper with only the nodes of the current wrapper that, when passed into the provided predicate function, return true."(返回一个新的 wrapper,其中只包含当前 wrapper 中传入提供的谓词函数后返回true的那些节点。)这一点可以从文档原文 ReactWrapper/filterWhere.md 与 ShallowWrapper/filterWhere.md 两处得到印证。

参数详解:谓词函数收到的是什么

filterWhere的参数只有一个:谓词函数。它最关键、也最容易踩坑的约定是——谓词函数收到的参数不是裸的 React 节点,而是一个包装后的 wrapper 实例(即ReactWrapperShallowWrapper)。这意味着你可以在谓词内部直接调用该 wrapper 的全部实例方法,如.type().props().hasClass().name()等。

共享测试套件中的用例明确验证了这一约定(见 filterWhere.jsx):

const stub = sinon.stub(); stub.returns(true); const spy = sinon.spy(stub); wrapper.find('.foo').filterWhere(spy); expect(spy).to.have.property('callCount', 3); // 每个节点调用一次 expect(spy.args[0][0]).to.be.instanceOf(Wrapper); // 参数是包装后的节点 expect(spy.args[0][0].hasClass('bar')).to.equal(true); // 可直接调用 wrapper 方法

从测试中可以提炼出三个关键事实:

  1. 谓词函数对当前 wrapper 的每个节点各调用一次callCount等于节点数);
  2. 传入参数始终是该节点对应的 wrapper 实例instanceOf(Wrapper));
  3. 谓词返回false的节点会被剔除,返回true的节点被保留,且保留节点的顺序与原有顺序一致

官方示例与实战扩展

官方文档给出的示例(ReactWrapper/filterWhere.md)利用"复合组件节点的type()不是字符串"这一特性,从一批.foo节点中过滤出所有复合组件(即非 DOM 宿主节点):

const wrapper = mount(<MyComponent />); const complexComponents = wrapper.find('.foo').filterWhere((n) => typeof n.type() !== 'string'); expect(complexComponents).to.have.lengthOf(4);

在 enzyme 中,n.type()对宿主 DOM 元素(如<div>)返回字符串标签名(如'div'),而对复合组件返回组件构造函数或函数本身(非字符串),因此上面的断言可以精确筛选出"是组件而非原生元素"的节点。

结合谓词函数能调用 wrapper 方法的能力,还可以写出更多实用变体:

// 只保留带有指定 class 的节点 const active = wrapper.find('.item').filterWhere((n) => n.hasClass('active')); // 只保留传入了指定 props 的节点 const disabled = wrapper.find('button').filterWhere((n) => n.props().disabled === true); // 只保留 name 以特定前缀开头的复合组件 const widgets = wrapper.findWhere((n) => n.type() !== 'string') .filterWhere((n) => n.name().startsWith('Widget'));

注意最后一种组合中,findWhere是在整棵渲染树中递归查找,而filterWhere只对当前 wrapper 已有的节点集合做过滤,两者作用范围不同,不能混为一谈。

底层实现原理:三个方法共享的过滤核心

阅读源码可以发现,filterWherefilternot三个方法共享同一个私有核心函数filterWhereUnwrapped(见 ReactWrapper.js 与 ShallowWrapper.js):

function filterWhereUnwrapped(wrapper, predicate) { return wrapper.wrap(wrapper.getNodesInternal().filter(predicate).filter(Boolean)); }

这个核心函数只做了三件事:

  1. wrapper.getNodesInternal():取出当前 wrapper 内部的节点数组;
  2. .filter(predicate):对节点数组应用谓词过滤;末尾的.filter(Boolean)用于剔除过滤后可能残留的空值(如null/undefined节点),保证返回结果干净;
  3. wrapper.wrap(...):将过滤后的节点数组重新包装成新的 wrapper 实例返回。

而公共 APIfilterWhere本身只是一层薄封装,关键区别在于——它把原始节点再次包装后传给用户提供的谓词:

// ReactWrapper.js L593-595 filterWhere(predicate) { return filterWhereUnwrapped(this, (n) => predicate(this.wrap(n))); } // ShallowWrapper.js L1054-1056 filterWhere(predicate) { return filterWhereUnwrapped(this, (n) => predicate(this.wrap(n))); }

注意其中的this.wrap(n)谓词接收到的不是内部裸节点n,而是被wrap成 wrapper 的节点。这正是上一节"谓词收到的参数是 wrapper 实例"这一约定的源码出处,也解释了为什么可以在谓词内部调用.type().hasClass()等方法。

与 filter、not、hostNodes 的关联

filterWhere在整个 enzyme 的方法体系中处于基础地位,其他几个常用方法都建立在它之上(相关源码见 ReactWrapper.js 与 ShallowWrapper.js):

filter(selector):选择器版本的 filterWhere

filter(selector) { const predicate = buildPredicate(selector); return filterWhereUnwrapped(this, predicate); }

filter通过buildPredicate(selector)把 CSS 风格的选择器编译成一个谓词函数,再交给同一个filterWhereUnwrapped执行。两者的关系可以理解为:filter是"选择器即谓词"的特例,filterWhere是任意自定义逻辑的通用形式。官方文档 ReactWrapper/filter.md 也把两者互为关联方法列出。

not(selector):filterWhere 的取反

not(selector) { const predicate = buildPredicate(selector); return filterWhereUnwrapped(this, (n) => !predicate(n)); }

not在内部把选择器编译出的谓词取反后再次走filterWhereUnwrapped,等价于filterWhere((n) => !n.matches(selector))

hostNodes():官方内置的 filterWhere 应用

hostNodesfilterWhere最直接的"官方用例",它只保留真正渲染成 DOM 的宿主节点(见 ReactWrapper.js 与 ShallowWrapper.js):

hostNodes() { return this.filterWhere((n) => typeof n.type() === 'string'); }

它用typeof n.type() === 'string'作为谓词,与官方示例中的!== 'string'恰好互为取反——一个筛出 DOM 宿主节点,一个筛出复合组件节点。这个对照关系是理解type()返回值约定(宿主元素返回字符串标签名)的最佳切入点。

与 everyWhere / someWhere / findWhere 的区分

在 enzyme 的文档体系中,与filterWhere同族的方法还有:

  • .everyWhere(predicate):返回布尔值,判断是否所有节点都满足谓词(等价于filterWhere后长度不变,但语义更直接);
  • .someWhere(predicate):返回布尔值,判断是否存在至少一个节点满足谓词;
  • .findWhere(predicate):在整棵渲染树中递归查找满足谓词的节点,返回的新 wrapper 可能包含多级节点;
  • .filterWhere(predicate):仅对当前 wrapper 的直接节点集合做一层过滤,不做递归。

它们的谓词约定完全一致(都接收包装后的节点),差别仅在于遍历范围(一层 vs 整棵树)与聚合语义(保留 vs 判定)。filterWherefindWhere在 ReactWrapper 和 ShallowWrapper 中的实现也共用同一套Unwrapped辅助函数体系,便于保持一致的行为。

使用建议与注意事项

结合源码与测试用例,归纳几点工程实践建议:

  1. 谓词必须是纯函数且无副作用:谓词会对每个节点恰好调用一次,且结果仅用于决定节点去留;在其中调用setStatesetProps等变更方法会造成难以预期的行为。
  2. 基于选择器 vs 基于谓词的选择:能明确表达的需求优先用.filter(selector)(更简洁、可读性更好);当条件涉及 props 之间的组合逻辑、类型判断、多个 wrapper 方法联动时,再用filterWhere
  3. 链式调用是惯用法filterWhere返回新 wrapper,因此可以继续链式调用.map().forEach().some()等其余实例方法;官方示例中的wrapper.find('.foo').filterWhere(...)就是典型的"先定位再过滤"组合。
  4. 注意与mount/shallow的配套ReactWrapper版本配合mount()使用(需在 jsdom 等含 DOM 的环境下运行,详见 安装与运行指南),ShallowWrapper版本配合shallow()使用;两者的谓词参数分别是ReactWrapperShallowWrapper实例。
  5. 空结果的处理:若所有节点都被过滤掉,返回的是长度为 0 的新 wrapper(内部.filter(Boolean)会清除空值节点),可配合.exists().length属性做断言,不会抛出异常。

小结

.filterWhere(predicate)是 enzyme 节点查询体系中"以函数代替选择器"的通用入口:它以接收包装节点、返回布尔值的谓词为核心约定,通过共享的filterWhereUnwrapped核心逻辑实现不可变的过滤语义,并向下支撑着filternothostNodes等高频方法。理解它的参数约定与实现链路,能够让你在处理复杂节点筛选需求时写出更精准、更易维护的组件测试。完整的方法列表可查阅 ReactWrapper API 索引 与 ShallowWrapper API 索引 同目录下的对应文档。

【免费下载链接】enzymeJavaScript Testing utilities for React项目地址: https://gitcode.com/gh_mirrors/en/enzyme

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/20 17:57:20

车载SOA架构入门:从信号导向到SOME/IP与Adaptive Platform实战

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

作者头像 李华
网站建设 2026/9/20 17:57:13

整车静态电流监测方案:采样电阻与比较器电路设计及LTspice仿真

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

作者头像 李华
网站建设 2026/9/20 17:56:59

快速上手 PT 助手 Plus:PT-Plugin-Plus 浏览器插件完整使用教程

快速上手 PT 助手 Plus&#xff1a;PT-Plugin-Plus 浏览器插件完整使用教程 【免费下载链接】PT-Plugin-Plus PT 助手 Plus&#xff0c;为 Microsoft Edge、Google Chrome、Firefox 浏览器插件&#xff08;Web Extensions&#xff09;&#xff0c;主要用于辅助下载 PT 站的种子…

作者头像 李华
网站建设 2026/9/20 17:54:05

Atlas 300V 24G昇腾推理卡部署YOLO实战全流程

不少人第一次看到“Atlas 300V 24G”这个标题&#xff0c;第一反应都是“这玩意儿是不是一张运算加速卡”&#xff0c;接着又会问“能不能在上面部署YOLO”。我直接说结论&#xff1a;是的&#xff0c;这是昇腾的推理卡&#xff0c;专门干模型推理的活&#xff0c;而且用它在At…

作者头像 李华
网站建设 2026/9/20 17:52:35

ROS暑期学校全解析:从通信机制到仿真实操的机器人学习路径

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

作者头像 李华
网站建设 2026/9/20 17:51:43

银河麒麟OpenSSH漏洞修复:分清ssh与sshd,精准定位补丁源

1. 银河麒麟里“ssh”和“sshd”根本不是一回事——先分清谁在说话&#xff0c;再谈怎么修漏洞很多人一看到“OpenSSH漏洞”&#xff0c;第一反应就是翻出官网下载最新源码、解压、./configure、make、sudo make install——一套行云流水的操作下来&#xff0c;结果系统直接连不…

作者头像 李华