` 深入解析:通过 wrappingComponent 操控 Provider 与上下文)
测试前端【免费下载链接】enzymeJavaScript Testing utilities for React项目地址https://gitcode.com/gh_mirrors/en/enzyme点击查看免费下载导读getWrappingComponent()是 Enzyme 中与wrappingComponent选项配套的核心方法当你在mount()或shallow()时传入wrappingComponent选项Enzyme 会额外渲染一层“包裹组件”作为被测组件的父级而getWrappingComponent()则返回这层包裹组件对应的ReactWrapper/ShallowWrapper让你可以在测试中动态更新它的 props、state从而操控注入到被测组件中的 Context、Store 等外部依赖。读完本文你将掌握该方法的完整用法、底层实现原理、联动更新机制以及各种边界条件。本文以 docs/api/ReactWrapper/getWrappingComponent.md 为骨架结合 ReactWrapper.js、ShallowWrapper.js 源码与 getWrappingComponent.jsx 测试用例展开。方法签名与返回值.getwrappingComponent() ReactWrapper // 来自 mount() .getwrappingComponent() ShallowWrapper // 来自 shallow()返回值一个包裹着已渲染wrappingComponent的ReactWrappermount场景或ShallowWrappershallow场景。这个返回值可以被用来更新wrappingComponent的 props、state 等——例如通过setProps()替换一个 mock Store。注意getWrappingComponent()返回的并不是wrappingComponent自身类而是它渲染出的组件树在mount场景下其.type()等于wrappingComponent本身在shallow场景下则指向它渲染出的最外层元素详见下文“源码原理”。使用前提wrappingComponent与wrappingComponentProps选项getWrappingComponent()只在构造时传入了wrappingComponent选项的前提下才有意义。根据 mount.md 与 shallow.md 的说明两个入口共享以下选项options.wrappingComponentComponentType可选一个将渲染为node父级的组件可用于为被测组件提供 context、Store 等。注意wrappingComponent必须渲染它的 children否则 Enzyme 会抛错。options.wrappingComponentPropsObject可选如果指定了wrappingComponent则作为传入它的初始 props。getWrappingComponent()的典型工作流是用mount()/shallow()传入wrappingComponent如 Redux 的Provider、React Router 的Router调用wrapper.getWrappingComponent()拿到包裹组件的 wrapper用setProps()/setState()等 API 动态更新包裹组件进而影响被测组件读取的 context/store。官方示例动态切换 Redux Store下面是原文档的完整示例它展示了一个常见场景——测试 React-Redux 组件时通过getWrappingComponent()将真实的 Store 换成 mock Storeimport { Provider } from react-redux; import { Router } from react-router; import store from ./my/app/store; import mockStore from ./my/app/mockStore; function MyProvider(props) { const { children, customStore } props; return ( Provider store{customStore || store} Router {children} /Router /Provider ); } MyProvider.propTypes { children: PropTypes.node, customStore: PropTypes.shape({}), }; MyProvider.defaultProps { children: null, customStore: null, }; // mount 场景 const wrapper mount(MyComponent /, { wrappingComponent: MyProvider, }); const provider wrapper.getWrappingComponent(); provider.setProps({ customStore: mockStore }); // shallow 场景完全一致只需把 mount 换成 shallow // const wrapper shallow(MyComponent /, { // wrappingComponent: MyProvider, // }); // const provider wrapper.getWrappingComponent(); // provider.setProps({ customStore: mockStore });关键点在于MyProvider必须渲染childrenProvider与Router包裹着{children}因为wrappingComponent只有渲染出 children被测的MyComponent才能出现在组件树中getWrappingComponent()也才有意义。shallow()场景下的完整用法可参见 ShallowWrapper/getWrappingComponent.md两者 API 形态完全一致仅返回的 wrapper 类型不同。源码原理getWrappingComponent() 是如何实现的ReactWrappermount 场景在 ReactWrapper.js 中getWrappingComponent()的实现位于 L255-L263getWrappingComponent() { if (this[ROOT] ! this) { throw new Error(ReactWrapper::getWrappingComponent() can only be called on the root); } if (!this[OPTIONS].wrappingComponent) { throw new Error(ReactWrapper::getWrappingComponent() can only be called on a wrapper that was originally passed a wrappingComponent option); } return this[WRAPPING_COMPONENT]; }两个前置校验非常明确只能在根 wrapper 上调用this[ROOT] ! this时直接抛错必须原本就传入了wrappingComponent选项否则抛错提示“only be called on a wrapper that was originally passed awrappingComponentoption”。WRAPPING_COMPONENT是在构造时L122-L134创建的当isCustomComponent(options.wrappingComponent, adapter)为真时Enzyme 会调用渲染器的getWrappingComponentRenderer()实例化一个内部类WrappingComponentWrapperL1250-L1263并把它加入LINKED_ROOTS列表——这意味着包裹组件与主根是“双向联动”的更新包裹组件会同步更新主根反之亦然。WrappingComponentWrapper继承自ReactWrapper但重写了getWrappingComponent()并直接抛出TypeError从而阻止对包裹组件 wrapper 再次调用该方法。ShallowWrappershallow 场景在 ShallowWrapper.js 中getWrappingComponent()的实现位于 L536-L543逻辑与ReactWrapper版本完全对称getWrappingComponent() { if (this[ROOT] ! this) { throw new Error(ShallowWrapper::getWrappingComponent() can only be called on the root); } if (!this[OPTIONS].wrappingComponent) { throw new Error(ShallowWrapper::getWrappingComponent() can only be called on a wrapper that was originally passed a wrappingComponent option); } return this[WRAPPING_COMPONENT]; }Shallow 场景的构造过程更特殊makeShallowOptionsL344-L370在传入wrappingComponent时会调用适配器的wrapWithWrappingComponent()把被测节点包进RootFinder然后创建WrappingComponentWrapper并通过getContextFromWrappingComponent()深度渲染包裹组件把包裹组件产生的 legacy context 与createContext()的 Provider 值合并进主 wrapper 的options.contextL362-L369。这正是“wrappingComponent 为被测组件提供 context”这一能力的实现基础。Shallow 版WrappingComponentWrapperL1769-L1800额外重写了rerender()与setState()每次调用都会先执行父类逻辑再调用updatePrimaryRootContext()L1731-L1761重新从包裹组件提取 context 并同步给主 wrapper——因此当包裹组件的setState改变了 context 值时被测组件会随之重新渲染并获得新值。底层支撑adapter-utils 的 wrapWithWrappingComponent各 React 版本适配器共享的 enzyme-adapter-utils/src/Utils.js 中wrapWithWrappingComponent()L340-L350揭示了包装的最终形态export function wrapWithWrappingComponent(createElement, node, options) { const { wrappingComponent, wrappingComponentProps } options; if (!wrappingComponent) { return node; } return createElement( wrappingComponent, wrappingComponentProps, createElement(RootFinder, null, node), ); }即渲染出的树为wrappingComponentprops 来自wrappingComponentProps→RootFinder→ 被测节点。而getNodeFromRootFinder()L329-L338在找不到RootFinder时会抛出wrappingComponent must render its children!——再次印证了“包裹组件必须渲染 children”的硬性要求否则测试会在早期直接失败。边界条件与错误场景来自测试用例仓库的共享测试 packages/enzyme-test-suite/test/shared/methods/getWrappingComponent.jsx 覆盖了该方法的完整行为以下是几个必须注意的边界场景行为React ≤ 0.13 适配器构造时即抛TypeError: your adapter does not support \wrappingComponent. Try upgrading it!在非根 wrapper 上调用如wrapper.find(div).getWrappingComponent()抛ReactWrapper::getWrappingComponent() can only be called on the root在包裹组件自己的 wrapper 上再次调用同样抛“only be called on the root”错误内部类重写了该方法未传入wrappingComponent就调用抛...only be called on a wrapper that was originally passed a \wrappingComponent\ option包裹组件不渲染 children抛wrappingComponent must render its children!根已 unmount 后更新包裹组件mount 场景抛The wrapping component may not be updated if the root is unmounted.wrappingComponent未渲染 children抛wrappingComponent must render its children!测试同时验证了关键联动语义wrappingComponent.setProps()会影响被测组件更新contextValue后wrapper.text()随之变为新值wrappingComponent.setState()会驱动主 wrapper 重新渲染包裹组件 state 变化后被测组件树中出现了新增的StateTester节点主根 unmount 后包裹组件 wrapper 的 children 变为空验证了两者“链接”关系的解除。实战技巧用 getWrappingComponent() 操控 createContext针对 React ≥ 16.3 的React.createContext()测试用例演示了更现代的用法——直接把 Provider 放在wrappingComponent中通过setProps切换 Provider 的 valueconst Context React.createContext(); function WrappingComponent(props) { const { value, children } props; return Context.Provider value{value}{children}/Context.Provider; } const wrapper mount( Context.Consumer {(value) div{value}/div} /Context.Consumer, { wrappingComponent: WrappingComponent, wrappingComponentProps: { value: hello! }, }, ); const wrappingComponent wrapper.getWrappingComponent(); expect(wrapper.text()).to.equal(hello!); wrappingComponent.setProps({ value: goodbye! }); expect(wrapper.text()).to.equal(goodbye!);这里利用了wrappingComponentProps提供初始值再借getWrappingComponent()返回的 wrapper 完成运行中切换。由于浅渲染场景会经由updatePrimaryRootContext()同步 Provider 值源码中通过 referential comparison 判断值是否变化以决定是否重渲染Consumer 能立即读取到新值。总结getWrappingComponent()是把“外部依赖注入”与“测试中动态操控”衔接起来的关键 API。记住三条规则即可熟练使用先传wrappingComponent选项才能调用getWrappingComponent()且只能在根 wrapper 上调用wrappingComponent必须渲染 children否则无法挂载被测组件返回值与mount/shallow对应ReactWrapper或ShallowWrapper均可使用setProps、setState等常规 API且更新会自动联动主 wrapper。相关参考ReactWrapper 文档目录ShallowWrapper 文档目录以及wrappingComponent选项的完整说明见 mount.md 与 shallow.md。赞分享测试前端【免费下载链接】enzymeJavaScript Testing utilities for React项目地址https://gitcode.com/gh_mirrors/en/enzyme点击查看免费下载相关推荐深入解析 esp_lcd_jd9165在 ESP32-P4 上通过 MIPI-DSI 驱动 JD9165 LCD 控制器深入解析 esp_lcd_jd9165在 ESP32 P4 上通过 MIPI DSI 驱动 JD9165 LCD 控制器 导读 esp_lcd_jd9165物联网嵌入式驱动开发硬件开发Qwen Code External Context 扩展Provider 绑定上下文检索、Mem0 写入与自动召回深度解析Qwen Code External Context 扩展Provider 绑定上下文检索、Mem0 写入与自动召回深度解析 Qwen Code 的 Exte人工智能AI Agent代码智能体工具调用交互助手CLIQwenReact Context上下文原理Provider与Consumer实现全解析React Context上下文原理Provider与Consumer实现全解析 引言你还在为组件通信烦恼吗 在React开发中组件间的数据传递一直前端UI组件上一篇MinecraftForge异常处理最佳实践优雅应对运行时错误下一篇黑苹果安装革命OpCore Simplify一键自动化配置指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考