You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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事件监听:

  1. 第一个测试执行完毕后,Cypress会卸载当前组件,但之前通过window.addEventListener添加的resize事件并未被移除,仍然绑定在全局window对象上。
  2. 第二个测试执行时,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访问报错。

针对疑问的解答

  1. 为何组件卸载时a.value不存在?
    Vue组件卸载时,会自动将绑定DOM元素的ref置为null,这是Vue的内置机制,用于回收DOM引用、避免内存泄漏,符合框架的设计规范。

  2. 浏览器中运行无错误?
    浏览器中未出现错误,大概率是因为组件卸载后你没有触发window.resize事件;如果在组件卸载后手动调整浏览器窗口大小,同样会触发该报错。

  3. 无前置测试时Cypress中运行正常?
    无前置测试时,仅执行单个测试:组件挂载后,resize事件触发时组件仍处于挂载状态,a.value有有效值;测试结束后组件卸载,但此时测试流程已完成,Cypress不会再触发resize事件,因此不会暴露报错。

内容的提问来源于stack exchange,提问作者St. Jan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.09 04:38:24