Appearance
✨ 响应式和组件渲染 👌
要点速览
- 模板的本质是“渲染函数
render”,其返回虚拟 DOM;渲染器据此更新真实 DOM。 - 响应式的本质是“函数与数据的映射”:数据变更会让依赖该数据的函数(如
render)重新执行。 - 当
render在执行期间“读取拦截”到响应式数据成员时,就建立依赖;后续数据变化触发重新渲染。
模板编译与依赖建立
运行期关联的真实形态
- 源码级关联并非直接绑定到
render,而是通过组件的updateComponent调度关系来驱动。 - 模板中使用
ref会自动解包value,因此读取时触发访问器属性的“读取拦截”,从而建立依赖关系。
App.vue 示例:
html
<template>
<div>{{ name }}</div>
<div>{{ age }}</div>
</template>
<script setup>
import { ref } from "vue";
let name = ref("Bill");
let age = ref(18);
</script>编译可视化(vite-plugin-inspect)

- 在
setup中定义的响应式数据会变成__returned__对象的访问器属性;对该属性的读/写会被拦截。 - 在
_sfc_render中通过$setup.name、$setup.age访问这些访问器属性,产生“读取拦截”,从而与渲染函数建立依赖。
常见误区
- 仅变量重新赋值(非成员操作)不会触发拦截,自然也不会建立依赖。
- 在
ref包裹对象上改“对象成员”属于reactive的拦截路径,而非ref的拦截。
为什么 Vue 能实现精准更新
组件级更新的原因
- 响应式数据与组件的渲染函数建立依赖;数据变更时“该渲染函数”重新执行,驱动组件级更新。
- 渲染函数返回新虚拟 DOM,渲染器据此对真实 DOM 进行最小化变更。
- 节点级别的具体更新由 diff 算法确定。
- Vue2:双端 diff;Vue3:快速 diff。
为什么 Vue 能实现数据共享
简易共享思路
抽出一个单独的响应式数据源,多个组件读取它(在渲染函数中形成“读取拦截”),即可共享并联动更新。
jsx
import { reactive } from "vue";
export const store = reactive({
todos: [
{ id: 1, text: "学习Vue3", completed: false },
{ id: 2, text: "学习React", completed: false },
{ id: 3, text: "学习Angular", completed: false },
],
addTodo(todo) {
this.todos.push(todo);
},
toggleTodo(id) {
const todo = this.todos.find((todo) => todo.id === id);
if (todo) {
todo.completed = !todo.completed;
}
},
});Pinia 的作用
- Pinia 提供完整的工程化能力:开发者工具、热替换、插件机制、自动补全、SSR 等。
- 语义更明确:相比“一个抽出的
reactive对象”,Pinia 明确就是“全局共享的仓库”。
小结
- 模板背后是渲染函数;渲染函数在读取响应式数据成员时建立依赖。
- 数据变化 → 组件级别的渲染函数重新执行 → diff 确认节点更新 → 真实 DOM 更新。
- 共享数据可通过抽出响应式源或使用 Pinia,配合依赖建立实现联动更新。
