一个初始化值引发的 Vue 响应式 Bug 🐛

111 阅读2分钟

背景

最近在做新业务时,遇到了一个隐蔽的 Bug,花了一整个下午,好在也算是学到了一个新的 Vue 响应式的知识点。
事情经过如下……

事故现场

同事在 data 中声明了一个变量 planConfigQuery,并赋空值初始化:

data() {
  return {
    planConfigQuery: null
  }
}

这个变量用来从接口获取配置。接口调用成功后,他会这样赋值:

this.planConfigQuery = JSON.parse(res.data.data.configContent)

我在新业务中刚好需要用这个配置:

  • 如果有接口配置 → 用接口配置
  • 如果没有 → 用本地默认

于是写了一个计算属性:

offlineDraftColumn() {
  if (this.planConfigQuery && this.planConfigQuery.offlineDraftColumn) {
    return this.planConfigQuery.offlineDraftColumn
  } else {
    return this.TableColumns['offlineDraftColumn']
  }
}

到目前为止我都没发现问题......
不出意外的话,接下来就要出意外了......

Bug 登场

当我调用接口、改变了 planConfigQuery 后,表格配置竟然没更新!

  • 控制台打印 → 对的
  • 插值表达式输出到页面 → 对的
  • 表格渲染 → 还是原来的配置

由于需求很快要用,一时之间又找不到问题所在,只能用 :key 强制刷新表格,虽然能跑了,但心里总有块疙瘩:到底是什么问题导致的?

请教大老师

晚上下班后,回到家马上请教了gpt,在他的一行代码里找到了关键:

data(){
  return {
    planConfigQuery: {}  // 注意,不是 null
  }
}

我试着改成 {} 初始化 , 果然表格正常更新了

真相揭秘

问题出在 Vue 的响应式初始化阶段

  1. Vue 在组件 data() 初始化时,会递归将对象属性转为响应式(getter/setter)。
  2. 如果 planConfigQuery 初始化为 null,Vue 无法对它的子属性建立依赖跟踪(因为此时没有对象可递归)。
  3. 当你后面用一个新对象替换它时,这个对象的属性不是响应式的,依赖它的计算属性也不会自动更新。 所以,computed 虽然拿到了新值,但视图不会重新渲染。

Vue 响应式失效原因图

flowchart TD
    A["初始化 data()"] -->|"planConfigQuery: null"| B["Vue 无法递归建立响应式"]
    B --> C["依赖追踪缺失"]
    C --> D["后续替换为新对象"]
    D --> E["新对象属性不是响应式"]
    E --> F["computed 不触发更新 → 视图不刷新"]

正确写法

方法 1:初始化为对象

data() {
  return {
    planConfigQuery: {}
  }
}

方法 2:用 this.$set 替换对象

this.$set(this, 'planConfigQuery', JSON.parse(res.data.data.configContent))

收获与反思

  • 踩坑点:用 null 初始化响应式对象属性,后续替换时不会自动追踪子属性变化
  • 知识点:Vue 依赖收集发生在数据初始化阶段,对象必须在这时存在才能递归转为响应式
  • 调试建议
    • 数据变了但视图没更新 → 优先怀疑响应式问题
    • 尝试用 Vue.setthis.$set 替换验证

虽然学到了新东西,但我的宝贵时间啊……本来要开开心心回家打33远征队的,就这样被同事的坑浪费掉了 😭