背景
最近在做新业务时,遇到了一个隐蔽的 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 的响应式初始化阶段:
- Vue 在组件
data()初始化时,会递归将对象属性转为响应式(getter/setter)。 - 如果
planConfigQuery初始化为null,Vue 无法对它的子属性建立依赖跟踪(因为此时没有对象可递归)。 - 当你后面用一个新对象替换它时,这个对象的属性不是响应式的,依赖它的计算属性也不会自动更新。
所以,
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.set或this.$set替换验证
虽然学到了新东西,但我的宝贵时间啊……本来要开开心心回家打33远征队的,就这样被同事的坑浪费掉了 😭