` 完全指南:模擬組件卸載生命周期與驗(yàn)證清理邏輯)
Enzyme ShallowWrapper.unmount()完全指南模擬組件卸載生命周期與驗(yàn)證清理邏輯【免費(fèi)下載鏈接】enzymeJavaScript Testing utilities for React項(xiàng)目地址: https://gitcode.com/gh_mirrors/en/enzyme導(dǎo)讀.unmount()是 EnzymeShallowWrapper提供的一個(gè)生命周期模擬方法用于手動(dòng)卸載被淺渲染shallow render的組件從而在測(cè)試中真實(shí)觸發(fā)componentWillUnmount等清理鉤子。本文以 docs/api/ShallowWrapper/unmount.md 為骨架結(jié)合 ShallowWrapper 源碼、React 16 適配器實(shí)現(xiàn) 與 共享測(cè)試用例完整講解方法簽名、底層調(diào)用鏈、經(jīng)典示例、與ReactWrapper.unmount()的差異及實(shí)戰(zhàn)注意點(diǎn)。讀完你將掌握用 Enzyme 驗(yàn)證組件卸載清理邏輯的標(biāo)準(zhǔn)姿勢(shì)。方法簽名與返回值.unmount() Self參數(shù)無(wú)。返回值返回調(diào)用方自身ShallowWrapper支持鏈?zhǔn)秸{(diào)用。核心作用卸載當(dāng)前淺渲染的組件。官方文檔明確指出該方法“可用于模擬組件經(jīng)歷一次 unmount/mount卸載/掛載生命周期”即配合掛載操作可以完整復(fù)現(xiàn)組件從創(chuàng)建到銷(xiāo)毀再到重建的全過(guò)程。底層實(shí)現(xiàn)原理一次調(diào)用觸發(fā)的完整調(diào)用鏈ShallowWrapper 層的實(shí)現(xiàn)在 packages/enzyme/src/ShallowWrapper.js#L570-L576 中unmount()的實(shí)現(xiàn)非常精簡(jiǎn)unmount() { this[RENDERER].unmount(); if (this[ROOT][WRAPPING_COMPONENT]) { this[ROOT][WRAPPING_COMPONENT].unmount(); } return this; }其中RENDERER、ROOT、WRAPPING_COMPONENT是使用sym()創(chuàng)建的私有符號(hào)見(jiàn) ShallowWrapper.js#L37-L49對(duì)外不可見(jiàn)。該實(shí)現(xiàn)包含兩個(gè)關(guān)鍵動(dòng)作調(diào)用適配器渲染器的unmount()真正觸發(fā) React 層的卸載流程聯(lián)動(dòng)卸載wrappingComponent如果根 wrapper 配置了getWrappingComponent()關(guān)聯(lián)的包裝組件WRAPPING_COMPONENT會(huì)一并卸載保證整棵測(cè)試樹(shù)狀態(tài)一致。適配器層的實(shí)現(xiàn)淺渲染的渲染器由各 React 版本適配器創(chuàng)建。以 packages/enzyme-adapter-react-16/src/ReactSixteenAdapter.js#L561-L564 為例createShallowRenderer()內(nèi)部直接使用 React 官方react-test-renderer/shallow的ShallowRenderercreateShallowRenderer(options {}) { const adapter this; const renderer new ShallowRenderer(); ... return { ... unmount() { renderer.unmount(); }, ... }; }即ShallowWrapper.unmount()最終委托給 React 官方ShallowRenderer.unmount()由 React 自身負(fù)責(zé)調(diào)用類(lèi)組件實(shí)例的componentWillUnmount并完成卸載。不同 React 版本適配器如enzyme-adapter-react-13~enzyme-adapter-react-16.3在此處的職責(zé)一致只是底層 React 實(shí)現(xiàn)不同。與淺渲染初始化流程的呼應(yīng)unmount()的“對(duì)偶”操作發(fā)生在ShallowWrapper構(gòu)造函數(shù)中ShallowWrapper.js#L397-L432根 wrapper 創(chuàng)建時(shí)會(huì)調(diào)用adapter.createRenderer({ mode: shallow, ...options })創(chuàng)建渲染器、執(zhí)行首次render并在未禁用生命周期方法disableLifecycleMethods為false時(shí)手動(dòng)調(diào)用componentDidMount。而unmount()則負(fù)責(zé)銷(xiāo)毀這棵渲染樹(shù)兩者共同構(gòu)成完整的生命周期閉環(huán)。官方示例驗(yàn)證 componentWillUnmount 被調(diào)用文檔給出了一個(gè)可直接運(yùn)行的完整示例使用sinon的 spy 斷言卸載鉤子恰好被調(diào)用一次import PropTypes from prop-types; import sinon from sinon; const spy sinon.spy(); class Foo extends React.Component { constructor(props) { super(props); this.componentWillUnmount spy; } render() { const { id } this.props; return ( div className{id} {id} /div ); } } Foo.propTypes { id: PropTypes.string.isRequired, }; const wrapper shallow(Foo idfoo /); expect(spy).to.have.property(callCount, 0); wrapper.unmount(); expect(spy).to.have.property(callCount, 1);關(guān)鍵點(diǎn)拆解卸載前 spy 的callCount為0證明淺渲染階段componentWillUnmount不會(huì)被誤觸發(fā)調(diào)用wrapper.unmount()后callCount變?yōu)?證明卸載鉤子確實(shí)被 React 渲染器執(zhí)行組件使用PropTypes聲明必填的id屬性shallow(Foo idfoo /)傳入合法 props避免校驗(yàn)警告干擾測(cè)試。測(cè)試用例佐證行為契約與邊界情況倉(cāng)庫(kù)共享測(cè)試套件 packages/enzyme-test-suite/test/shared/methods/unmount.jsx 對(duì).unmount()定義了明確的行為契約1. 卸載鉤子被調(diào)用且無(wú)參數(shù)it(calls componentWillUnmount(), () { const spy sinon.spy(WillUnmount.prototype, componentWillUnmount); const wrapper Wrap(WillUnmount idfoo /); expect(spy).to.have.property(callCount, 0); wrapper.unmount(); expect(spy).to.have.property(callCount, 1); const [args] spy.args; expect(args).to.eql([]); });該用例不僅斷言componentWillUnmount恰好被調(diào)用一次還斷言調(diào)用時(shí)未傳入任何參數(shù)args為空數(shù)組這與 React 生命周期約定一致。2. 非 root 限制僅在 mount 模式生效itIf(!isShallow, throws on non-root, () { const wrapper WrapRendered(WillUnmount idfoo /); const span wrapper.find(span); expect(span).to.have.lengthOf(1); expect(() span.unmount()).to.throw(Error); });該用例通過(guò)itIf(!isShallow, ...)條件執(zhí)行只有當(dāng)isShallow為false即mount模式時(shí)對(duì)子節(jié)點(diǎn) wrapper 調(diào)用unmount()才會(huì)拋出異常。反過(guò)來(lái)可以確認(rèn)在ShallowWrapper中由于渲染器屬于根 wrapperunmount()實(shí)際作用于整棵渲染樹(shù)因此對(duì)子節(jié)點(diǎn) wrapper 調(diào)用并不會(huì)額外拋錯(cuò)——但語(yǔ)義上仍建議在根 wrapper 上調(diào)用以保證行為清晰可預(yù)期。與ReactWrapper.unmount()的區(qū)別ReactWrappermount模式同樣提供unmount()但其實(shí)現(xiàn)包含嚴(yán)格的 root 校驗(yàn)packages/enzyme/src/ReactWrapper.js#L302-L311unmount() { if (this[ROOT] ! this) { throw new Error(ReactWrapper::unmount() can only be called on the root); } this.single(unmount, () { this[RENDERER].unmount(); this.update(); }); return this; }兩者差異總結(jié)如下維度ShallowWrapper.unmount()ReactWrapper.unmount()root 限制無(wú)顯式 root 校驗(yàn)非 root 調(diào)用直接拋錯(cuò)卸載后動(dòng)作返回自身調(diào)用this.update()同步 enzyme 樹(shù)快照底層渲染器ReactShallowRendererReactDOM.unmountComponentAtNode掛載渲染器適用場(chǎng)景淺渲染組件生命周期模擬真實(shí) DOM 掛載組件的生命周期模擬此外ReactWrapper還提供了detach()ReactWrapper.js#L1222-L1230用于在attachTo場(chǎng)景下將組件從真實(shí) DOM 上摘除。實(shí)戰(zhàn)應(yīng)用場(chǎng)景場(chǎng)景一模擬 unmount/mount 完整生命周期文檔明確指出unmount()的用途是“模擬組件經(jīng)歷一次 unmount/mount 生命周期”。典型模式是const wrapper shallow(Foo idfoo /); // ... 第一輪斷言 wrapper.unmount(); // ... 驗(yàn)證 componentWillUnmount 相關(guān)清理邏輯 wrapper.mount(); // 重新掛載ShallowWrapper 同樣提供 mount()詳見(jiàn) docs/api/ShallowWrapper/mount.md // ... 繼續(xù)第二輪斷言這種模式適合驗(yàn)證組件在多次掛載/卸載循環(huán)中的狀態(tài)重置、事件解綁、定時(shí)器清理等行為。場(chǎng)景二驗(yàn)證資源清理邏輯對(duì)于需要清理定時(shí)器、取消訂閱、斷開(kāi)連接等資源的組件unmount()是唯一能在淺渲染模式下觸發(fā)清理鉤子的途徑class TimerComponent extends React.Component { componentDidMount() { this.timer setInterval(this.tick, 1000); } componentWillUnmount() { clearInterval(this.timer); } ... } const wrapper shallow(TimerComponent /); wrapper.unmount(); // 斷言 timer 已被清理無(wú)泄漏場(chǎng)景三與 simulateError 等鉤子配合當(dāng)組件同時(shí)涉及componentDidCatch或錯(cuò)誤邊界邏輯時(shí)unmount()可在測(cè)試末尾顯式清理渲染樹(shù)避免影響后續(xù)測(cè)試用例的隔離性。注意事項(xiàng)卸載后 wrapper 的狀態(tài)unmount()之后組件實(shí)例已被銷(xiāo)毀此時(shí)再通過(guò) wrapper 訪問(wèn)instance()或觸發(fā)交互會(huì)得到不符合預(yù)期的結(jié)果如需繼續(xù)斷言應(yīng)先重新掛載或重新shallow。disableLifecycleMethods選項(xiàng)的影響淺渲染默認(rèn)會(huì)在構(gòu)造函數(shù)階段調(diào)用componentDidMountShallowWrapper.js#L417-L430但當(dāng)disableLifecycleMethods: true時(shí)生命周期方法被跳過(guò)此時(shí)unmount()對(duì)componentWillUnmount的觸發(fā)行為也會(huì)相應(yīng)改變編寫(xiě)斷言前需確認(rèn)配置。wrappingComponent 聯(lián)動(dòng)卸載通過(guò)getWrappingComponent()關(guān)聯(lián)的包裝組件會(huì)隨unmount()一并卸載無(wú)需手動(dòng)處理。建議在 root wrapper 上調(diào)用雖然ShallowWrapper.unmount()沒(méi)有像ReactWrapper那樣的 root 校驗(yàn)但其作用于整棵渲染樹(shù)在非根子 wrapper 上調(diào)用容易造成語(yǔ)義誤解推薦始終通過(guò)wrapper.root().unmount()或在根實(shí)例上調(diào)用。小結(jié)ShallowWrapper.unmount()用一行調(diào)用把 React 淺渲染器的卸載流程暴露給測(cè)試代碼從 ShallowWrapper.js#L570 的方法實(shí)現(xiàn)到適配器層對(duì)ShallowRenderer.unmount()的委托再到共享測(cè)試套件對(duì)componentWillUnmount恰好調(diào)用一次且無(wú)參的契約斷言整條鏈路清晰可循。掌握它你就能在 Enzyme 淺渲染測(cè)試中精確驗(yàn)證組件的清理邏輯與完整的掛載-卸載生命周期?!久赓M(fèi)下載鏈接】enzymeJavaScript Testing utilities for React項(xiàng)目地址: https://gitcode.com/gh_mirrors/en/enzyme創(chuàng)作聲明:本文部分內(nèi)容由AI輔助生成(AIGC),僅供參考