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 内部实现filter、not、hostNodes等方法的底层基础设施。读完本文,你将掌握filterWhere的完整签名、谓词函数的调用约定、底层实现原理,以及在实际测试中组合使用它的实战技巧。
方法签名与返回值
在 enzyme 中,ReactWrapper(由mount()产生)和ShallowWrapper(由shallow()产生)都提供了同名方法filterWhere,两者的语义与用法完全一致,仅返回的 wrapper 类型不同:
// ReactWrapper 版本 .filterWhere(predicate) => ReactWrapper // ShallowWrapper 版本 .filterWhere(predicate) => ShallowWrapper- 参数:
predicate(ReactWrapper => Boolean或ShallowWrapper => 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 实例(即ReactWrapper或ShallowWrapper)。这意味着你可以在谓词内部直接调用该 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 方法从测试中可以提炼出三个关键事实:
- 谓词函数对当前 wrapper 的每个节点各调用一次(
callCount等于节点数); - 传入参数始终是该节点对应的 wrapper 实例(
instanceOf(Wrapper)); - 谓词返回
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 已有的节点集合做过滤,两者作用范围不同,不能混为一谈。
底层实现原理:三个方法共享的过滤核心
阅读源码可以发现,filterWhere、filter、not三个方法共享同一个私有核心函数filterWhereUnwrapped(见 ReactWrapper.js 与 ShallowWrapper.js):
function filterWhereUnwrapped(wrapper, predicate) { return wrapper.wrap(wrapper.getNodesInternal().filter(predicate).filter(Boolean)); }这个核心函数只做了三件事:
wrapper.getNodesInternal():取出当前 wrapper 内部的节点数组;.filter(predicate):对节点数组应用谓词过滤;末尾的.filter(Boolean)用于剔除过滤后可能残留的空值(如null/undefined节点),保证返回结果干净;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 应用
hostNodes是filterWhere最直接的"官方用例",它只保留真正渲染成 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 判定)。filterWhere与findWhere在 ReactWrapper 和 ShallowWrapper 中的实现也共用同一套Unwrapped辅助函数体系,便于保持一致的行为。
使用建议与注意事项
结合源码与测试用例,归纳几点工程实践建议:
- 谓词必须是纯函数且无副作用:谓词会对每个节点恰好调用一次,且结果仅用于决定节点去留;在其中调用
setState、setProps等变更方法会造成难以预期的行为。 - 基于选择器 vs 基于谓词的选择:能明确表达的需求优先用
.filter(selector)(更简洁、可读性更好);当条件涉及 props 之间的组合逻辑、类型判断、多个 wrapper 方法联动时,再用filterWhere。 - 链式调用是惯用法:
filterWhere返回新 wrapper,因此可以继续链式调用.map()、.forEach()、.some()等其余实例方法;官方示例中的wrapper.find('.foo').filterWhere(...)就是典型的"先定位再过滤"组合。 - 注意与
mount/shallow的配套:ReactWrapper版本配合mount()使用(需在 jsdom 等含 DOM 的环境下运行,详见 安装与运行指南),ShallowWrapper版本配合shallow()使用;两者的谓词参数分别是ReactWrapper与ShallowWrapper实例。 - 空结果的处理:若所有节点都被过滤掉,返回的是长度为 0 的新 wrapper(内部
.filter(Boolean)会清除空值节点),可配合.exists()、.length属性做断言,不会抛出异常。
小结
.filterWhere(predicate)是 enzyme 节点查询体系中"以函数代替选择器"的通用入口:它以接收包装节点、返回布尔值的谓词为核心约定,通过共享的filterWhereUnwrapped核心逻辑实现不可变的过滤语义,并向下支撑着filter、not、hostNodes等高频方法。理解它的参数约定与实现链路,能够让你在处理复杂节点筛选需求时写出更精准、更易维护的组件测试。完整的方法列表可查阅 ReactWrapper API 索引 与 ShallowWrapper API 索引 同目录下的对应文档。
【免费下载链接】enzymeJavaScript Testing utilities for React项目地址: https://gitcode.com/gh_mirrors/en/enzyme
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考