为什么数据变了界面却不动?——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:变更检测必须跑

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

相关推荐
传奇开心果编程9 小时前
【传奇开心果系列】Flet框架平面级联菜单侧边栏和和登录用户圆形头像自定义组件模板
python·ui·前端框架
胡西风_foxww14 小时前
Jotai:React轻量级状态管理新选择
前端·react.js·前端框架·状态管理·jotai
qb15 小时前
vue3.5.18源码:一文搞懂ref和发布订阅者模式之间的关系?
前端·vue.js·前端框架
Jacob02342 天前
为何现代 JavaScript 框架越来越像?
javascript·react.js·前端框架
我想说一句2 天前
有了TailwindCSS,CSS不再需要你写了!
前端·css·前端框架
just小千2 天前
重学React(三):状态管理
前端·react.js·前端框架
Casta-mere2 天前
React SSR 水合问题
前端·react.js·前端框架·ssr
野区小女王2 天前
React函数组件灵魂搭档:useEffect深度通关指南!
前端·react.js·前端框架
我想说一句2 天前
bubu智聘App亮点详解(2) Coze工作流接入
前端·前端框架·trae