Skip to content

✨ 响应式和组件渲染 👌

要点速览

  • 模板的本质是“渲染函数 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,配合依赖建立实现联动更新。