本节摘要:数据驱动指"界面是数据的函数"——数据决定界面"应该是什么样"。响应式系统则回答触发时机问题:Vue 用依赖追踪自动发现"谁该更新",React 用显式触发告诉组件"更新发生了"。两条路线是理解两框架差异最关键的一把钥匙。
本节阅读目标
阅读完本节,你应当能够:
"界面是数据的函数"这六个字,是 Data-driven UI 的内核。同样一个用户列表,数据是"空数组",界面是空态提示;数据填满,界面出现列表。你只管维护数据,界的"长相"由框架算出来。这跟第 1.1 节"声明式"是一回事的两面:声明式是理念,数据驱动是它在状态维度的体现。
数据驱动带来的直接好处:状态源唯一,界面是它的投影,调试时改动数据就能复现问题,不必猜测是哪个 DOM 忘了同步。
数据驱动把"要不要更新的判断"从开发者转移到框架。这个难题拆两半:
React 与 Vue 的分野,正是在这两步上选了不同分工。
Vue 在读取数据时记下"这个组件依赖了这份数据",形成一张依赖表;数据一变,框架翻表,精确通知依赖它的组件。只有被依赖的组件重跑,粒度细、无谓更新少。变化感知靠数据劫持(各版本的 Proxy / 访问器实现),传导靠依赖表。
React 里状态更新通过 setState 或 useState 的 setter 触发,从该组件起整棵子树重新渲染(配合虚拟 DOM diff 收敛最终变更)。它不做"谁依赖谁"的精细追踪,而是"这个节点被标脏了,以它为根往下重渲"。更直白,但无谓重渲染的机会更多,需要靠 memo 等优化手段控制粒度。
一个对照表把差异摊开:
| 维度 | Vue(依赖追踪) | React(显式触发) |
|---|---|---|
| 感知变化 | 数据劫持自动感知 | 调用 setter 主动触发 |
| 传导粒度 | 精确到依赖组件的函数 | 以组件为根的整树重渲 |
| 无谓更新 | 天然较少 | 依赖 memo 等手动约束 |
| 心智 | 少管"何时更新" | 开发者需理解渲染时机 |

纸上谈兵总归虚,各看一段最贴近直觉的骨架。
Vue 端,依赖追踪的"记录"是隐形发生的——你在 template 里读 user.name,框架在读取的一刻把当前组件记进 user 的依赖表;之后你哪怕在别处改了 user.name,框架翻表就能只通知"那一处":
// 简化示意:读取时登记、写入时派发 let _reactive = new Map(); function read(key, subscriber) { /* 把 subscriber 记进依赖表 */ } function mutate(key, value) { /* 翻依赖表,逐个通知订阅者 */ }
React 端没有这张表,setState 一调用就沿组件树向下标脏整棵子树再去 diff 收敛:
setUser((prev) => ({ ...prev, name: newName })); // 触发的是"以本组件为根的整块重渲"
把这两段并排看,你能一眼分出差别:Vue 告诉你"是哪几处变了",React 告诉你"从哪个组件往下重渲"。2.2 提到虚拟 DOM 负责把 React 的粗粒度重渲"收敛成最小变更",正是为了补上 React 显式触发那条更粗暴的路径。
细粒度追踪让 Vue 居中省心,但"何时重渲"对读者更透明的要求较低;React 把渲染时机暴露给你,让你拥有更强的控制力,代价是要懂背后的时机。两者没有绝对优劣,但理解这条对照之后:你在 Vue 里会少写很多不必要的优化,在 React 里会主动去防无谓重渲染。第 3、4 章的代码练习会不断回到这张表。
两条触发路线的差异,最终会落在你写代码时的两个习惯上。在 Vue 里,你基本可以"少操心何时更新"——只要保证数据走的是响应式通道,改了界面自己会跟上,依赖表兜住了"谁该动"。在 React 里,你要有点"渲染时机感":每次 setState 都意味着"从这里往下一整棵子树要重渲",于是你会发现需要偶尔用 memo 等手段把"没有变化的分支"挡在重渲之外。
这不是谁优谁劣的对比,而是两套纪律。对新同学常有一个现象:刚从 Vue 迁到 React 的人,容易把"数据变了界面没动"归咎于 bug,其实往往只是没有走 setState 这条唯一的触发通道;反之,刚从 React 迁到 Vue 的人,会过度手动控制渲染,做了很多不必要的优化。心里这幅"谁触发、谁重渲"的图,正是跨框架不踩坑的那根弦。
把抽象落成一次具体的改动,最能检验你懂没懂两条路线。假设页面上有一个用户卡片粗组件和一个顶栏,顶栏里显示"当前用户:阿明"。现在用户把昵称改成"阿翔"。在 Vue 里,改的是 user.name 这个响应式数据,框架翻依赖表发现只有顶栏那处组件读了它,于是只重渲顶栏;卡片里的头像、简介只要没读 user.name,就纹丝不动。在 React 里,setUser({ ...prev }) 一调用,从持有这份 state 的那个组件往下,整棵子树都被标脏重渲,再由虚拟 DOM diff 把最终变更收敛到真正变化的那几处。
这两条路径的结果在屏幕上可能一模一样——都只更新了"阿翔"两个字。差别在过程:Vue 是从"数据被哪些组件依赖"逆向算出来,React 是从"从哪个组件往下重渲"正向推下去。于是工程上出现了一条可验证的经验:当 React 页面出现"明明改一个数据,却有一大片地方都在重跑效率"这类问题时,八成是没做 memo 收敛;而当 Vue 出现"改了数据居然没反应"时,八成是数据没走响应式通道(比如直接解构、用非响应式容器装了它)。一对判断,正好都掉头指向本节这张对照表的两端。
下一节接着讲组件从创建到销毁的时机问题——生命周期管理,看看"该在哪一步做数据加载、在哪一步做资源清理"。