Vue3应用在Cypress中修改视口时出现TypeError: ref.value为null
Cypress中Vue3组件视口切换报错“TypeError: a.value is null”的原因与解决办法
问题重现
组件代码(Test.vue)
<script setup> import { ref, onMounted } from 'vue' const a = ref(null) onMounted(() => { function onWindowResize() { window.addEventListener("resize", () => { // 窗口 resize 时更新内容 a.value.textContent = 'Text displayed here will be decided on the window size' }) } onWindowResize() }) </script> <template> <div> <span ref="a" data-testid="test1">M</span> <a> ..more</a> </div> </template>
Cypress测试代码
import Test from '../Test.vue' describe('run 2 test', () => { it('The error only appears if I run a test before I run the viewport change', () => { cy.mount(Test) cy.get('[data-testid="test1"]').get('a').should('contain', ' ..more') }) it('The error originates on the first viewport changes it appears', () => { cy.mount(Test) // 此处触发视口变更,报错出现 cy.viewport(550, 750) let charCount = 0 cy.get('[data-testid="test1"]').then(($span) => { charCount = String($span.text()).length; }) cy.viewport(400, 400) cy.get('[data-testid="test1"]').then(($span) => { const newCharCount = String($span.text()).length; expect(newCharCount).to.be.eq(charCount) }) }) })
原因分析
核心问题是组件卸载时未清理绑定在window上的resize事件监听:
- 第一个测试执行完毕后,Cypress会卸载当前组件,但之前通过
window.addEventListener添加的resize事件并未被移除,仍然绑定在全局window对象上。 - 第二个测试执行时,
cy.viewport()会触发window的resize事件,此时第一个测试中已卸载组件的resize回调会被执行——但该组件已销毁,a.value已被Vue置为null,因此访问a.value.textContent时会抛出TypeError。
解决办法
在组件的onUnmounted钩子中移除resize事件监听,注意需要保存事件回调的引用,确保removeEventListener能匹配到对应的监听函数:
<script setup> import { ref, onMounted, onUnmounted } from 'vue' const a = ref(null) // 保存resize回调的引用,用于后续移除监听 const resizeHandler = () => { if (a.value) { // 先判断a.value是否存在,避免报错 a.value.textContent = 'Text displayed here will be decided on the window size' } } onMounted(() => { window.addEventListener("resize", resizeHandler) }) onUnmounted(() => { // 组件卸载时移除事件监听 window.removeEventListener("resize", resizeHandler) }) </script> <template> <div> <span ref="a" data-testid="test1">M</span> <a> ..more</a> </div> </template>
额外添加if (a.value)的判断是双重保险:即使事件监听未被及时移除,也能避免null访问报错。
针对疑问的解答
为何组件卸载时a.value不存在?
Vue组件卸载时,会自动将绑定DOM元素的ref置为null,这是Vue的内置机制,用于回收DOM引用、避免内存泄漏,符合框架的设计规范。浏览器中运行无错误?
浏览器中未出现错误,大概率是因为组件卸载后你没有触发window.resize事件;如果在组件卸载后手动调整浏览器窗口大小,同样会触发该报错。无前置测试时Cypress中运行正常?
无前置测试时,仅执行单个测试:组件挂载后,resize事件触发时组件仍处于挂载状态,a.value有有效值;测试结束后组件卸载,但此时测试流程已完成,Cypress不会再触发resize事件,因此不会暴露报错。
内容的提问来源于stack exchange,提问作者St. Jan
相关产品推荐
相关产品推荐

