为什么数据变了界面却不动?——Vue / React / Angular 常见渲染“失效”场景全解析

引言

在现代前端框架中,数据驱动视图已经是标配。理论上,数据一变,UI 就应该自动更新。

然而,实际开发中,不论是 Vue、React 还是 Angular,都可能出现"数据改了但界面没动"的情况。

这并不是框架失灵,而是我们踩中了各自的机制限制。本文将系统梳理这些"视图不更新"的真相,并给出可落地的解决方案。

一、Vue 2:defineProperty 时代的老毛病

Vue 2 使用 Object.defineProperty 劫持数据,初始化时只会追踪已有属性。

常见不更新场景:

  1. 对象新增 / 删除属性

    js 复制代码
    this.obj.newKey = 1; // ❌ 不更新
    delete this.obj.x;   // ❌ 不更新
    this.$set(this.obj, 'newKey', 1); // ✅
  2. 数组按索引改值 / 改 length

    js 复制代码
    this.arr[1] = 'x'; // ❌
    this.$set(this.arr, 1, 'x'); // ✅
    this.arr.splice(1, 1, 'x'); // ✅
  3. 深层变更没被 watch 到

    js 复制代码
    watch(obj, fn); // 默认浅监听
    watch(() => obj.a.b, fn); // ✅ 精确监听
    watch(obj, fn, { deep: true }); // ✅ 深监听
  4. v-for 用 index 做 key

    • DOM 复用导致错位,必须用业务唯一 ID 做 :key
  5. 更新已发生但还没渲染

    • 需要 await this.$nextTick() 再读取 DOM
  6. keep-alive 缓存

    • 切换路由/标签页时需要 :key 触发刷新
  7. 响应式对象被替换成非响应式

    • 比如 Object.freeze 的对象、原型属性
  8. Class 实例 / 原型链字段

    • Vue 无法追踪原型链上的变更

二、Vue 3:Proxy 时代的新坑

Vue 3 用 Proxy 解决了新增属性/数组索引不更新的问题,但也有一些"看似更新"的陷阱。

常见不更新场景:

  1. 解构导致丢失响应性

    js 复制代码
    const { a } = reactiveObj; // ❌
    const { a } = toRefs(reactiveObj); // ✅
  2. ref 在 JS 中没 .value

    js 复制代码
    count++; // ❌
    count.value++; // ✅
  3. shallowReactive / shallowRef 只追踪浅层

    • 深层改值需 triggerRef
  4. 直接修改 props

    • 必须通过 emit('update:xxx') 或本地副本
  5. watch 依赖没写对

    • 默认浅监听,需 watch getter 或 { deep: true }
  6. key 复用 / keep-alive 同 Vue 2

  7. 对 markRaw / readonly 对象改值

    • 本来就不会触发
  8. 异步更新批处理

    • 改很多次值,DOM 只会最后更新一次,需要立即读取 DOM 用 await nextTick()

三、React:引用没变就不渲染

React 的渲染依赖 state 引用变化,而不是深比较。

常见不更新场景:

  1. 直接改 state 而不 setState

    js 复制代码
    this.state.count++; // ❌
    this.setState({ count: this.state.count + 1 }); // ✅
  2. 深层对象/数组直接改值

    js 复制代码
    user.name = 'B';
    setUser(user); // ❌ 引用没变
    setUser({ ...user, name: 'B' }); // ✅
  3. PureComponent / React.memo 下引用没变

    • 浅比较返回 true → 不渲染
  4. 闭包陷阱

    js 复制代码
    setTimeout(() => setCount(count + 1), 1000); // ❌ count 旧值
    setTimeout(() => setCount(c => c + 1), 1000); // ✅
  5. shouldComponentUpdate 返回 false

    • 手动阻止了渲染
  6. Context 没更新

    • Provider value 引用没变 → 消费者不更新

四、Angular:变更检测没跑

Angular 依赖 Zone.js 触发变更检测。

常见不更新场景:

  1. OnPush 策略下引用没变

    js 复制代码
    this.user.name = 'B'; // ❌
    this.user = { ...this.user, name: 'B' }; // ✅
  2. Zone 之外改值

    • 需要 this.zone.run(() => { ... })
  3. 第三方库回调不触发检测

    • 手动调用 ChangeDetectorRef.detectChanges()

五、三大框架的共性坑

  1. 列表 key 复用导致错位
  2. 数据源不是响应式(冻结对象、原型链字段)
  3. DOM 读取时机错误(改数据后立即读 DOM)
  4. 组件缓存(keep-alive / memo / OnPush)

六、如何避免"改了不更新"

  • 理解框架的响应式原理
  • 遵循框架提供的状态修改 API
  • 对对象/数组改值时,优先用新引用
  • 必要时用强制刷新(Vue $forceUpdate / React forceUpdate / Angular detectChanges
  • 列表渲染一定要用稳定唯一的 key

七、总结

  • Vue 2 :最怕新增属性、数组索引 → 用 $set 或替换引用
  • Vue 3:解构丢响应、ref.value、浅响应等细节
  • React:引用必须变化
  • Angular:变更检测必须跑

理解了这些"真相",你就能在第一时间判断出问题出在哪,并用最短的时间修复它。

相关推荐
Baklib梅梅3 小时前
2025 年 8 个最佳网站内容管理系统(CMS)
前端·ruby on rails·前端框架·ruby
奋进的电子工程师4 小时前
汽车软件研发智能化:AI在CI/CD中的实践
人工智能·ci/cd·汽车·软件工程·软件构建·代码规范
叫我阿柒啊5 小时前
从Java全栈到Vue3实战:一次真实面试的深度复盘
java·spring boot·微服务·vue3·响应式编程·前后端分离·restful api
知识分享小能手10 小时前
React学习教程,从入门到精通, React 入门指南:React JSX 语法知识点详解及案例代码(8)
前端·javascript·vue.js·学习·react.js·前端框架·anti-design-vue
大怪v16 小时前
老乡,别走!Javascript隐藏功能你知道吗?
前端·javascript·代码规范
叫我阿柒啊18 小时前
从Java全栈到前端框架:一次真实的面试对话
java·spring boot·微服务·前端框架·vue3·全栈开发
叫我阿柒啊21 小时前
Java全栈开发工程师面试实战:从基础到微服务的完整技术演进
java·spring boot·微服务·前端框架·vue3·全栈开发·面试技巧
EndingCoder1 天前
数据库集成:使用 SQLite 与 Electron
数据库·electron·sqlite·前端框架·node.js
Craze_rd1 天前
Go 开发规范1
go·代码规范
开发者小天1 天前
在Ant Design Vue 中使用图片预览的插件
前端·javascript·vue.js·前端框架