Vue 组件渲染流程

Vue 组件从创建到更新,经历了哪些阶段?

Vue 组件的一次更新可以简化为:

创建组件实例
→ setup / 选项初始化
→ 创建 render effect
→ 执行 render
→ 生成 VNode
→ mount / patch
→ 执行生命周期和副作用

1. 创建组件实例

当 renderer 遇到组件 VNode 时,会创建组件实例。实例会保存:

  • props;
  • attrs;
  • slots;
  • emits;
  • setup 状态;
  • 生命周期和渲染相关信息。

Vue 3 中,组件的逻辑主要由 setup() 和 render function 组成;使用 <script setup> 时,编译器会把代码转换为 setup 相关实现。

2.setup 与 render

<script setup>
import { ref } from 'vue';

const count = ref(0);
</script>

<template>
  <button @click="count++">{{ count }}</button>
</template>

模板会被编译为 render function。首次渲染时,render effect 执行,读取 count,响应式系统因此建立依赖关系。

setup() 本身通常只在组件创建时执行一次;render 则可能执行多次。不要把每次渲染都要做的逻辑误放进 setup,也不要在 render 中执行副作用。

3. Mount:首次挂载

首次渲染时,Vue 会:

  1. 执行组件的 render function;
  2. 生成组件子树的 VNode;
  3. 递归创建元素、文本、子组件;
  4. 将真实 DOM 插入宿主容器;
  5. 执行 mounted 等生命周期。

简化流程:

组件 render
组件子树 VNode
patch(null, vnode)
创建 DOM 并插入
mounted

4. Update:响应式更新

当 render 中读取的响应式数据发生变化时,响应式系统会通知组件的 render effect。组件更新任务进入 scheduler 队列,刷新时重新执行 render,得到新的 VNode。

state 变化
触发组件 render effect
newVNode = render()
patch(oldVNode, newVNode)
只更新必要 DOM

组件重新执行 render 不代表所有 DOM 都重新创建。renderer 会比较新旧 VNode 的类型、props、子节点和 key,决定复用、更新、移动或删除。

5. 父子组件更新

父组件 render 产生新的子组件 VNode 后,Vue 会通过 props、slots 和 VNode 身份协调子组件:

  • props 变化时,子组件会收到新的 props 并更新;
  • props 没有变化时,子组件不一定需要重新渲染;
  • 子组件自己的响应式状态变化,可以独立触发子组件更新;
  • slot 内容由父组件生成,父组件更新可能导致 slot 内容重新计算。

组件更新和 DOM 更新不是同一个概念。组件 render 可能重新执行,但 patch 发现没有变化后,可以不修改 DOM。

6. 生命周期与渲染阶段

常见顺序可以简化为:

beforeCreate / setup
→ beforeMount
→ mounted
→ beforeUpdate
→ updated
→ beforeUnmount
→ unmounted

Composition API 中对应的生命周期注册方式包括 onMountedonUpdatedonBeforeUnmount 等。需要读取挂载后的 DOM 时使用 onMounted;需要读取更新后的 DOM 时,通常使用 onUpdated 或等待 nextTick

生命周期回调也应避免制造无限更新:在 updated 中再次无条件修改响应式状态,可能导致循环更新。

7. VNode、组件实例和 DOM 的关系

这三者不是同一个东西:

组件实例:保存 props、setup 状态和生命周期
VNode:描述某次渲染结果
DOM:浏览器中的真实宿主节点

一个组件实例可以在多次更新中生成多个 VNode;一个组件也可能渲染多个 DOM 节点。VNode 是协调过程的中间描述,不是 DOM 本身。

8.nextTick 在流程中的位置

count.value++;
await nextTick();
// 此时通常可以读取本轮更新后的 DOM

nextTick 等待 Vue 刷新当前更新队列,适合在状态修改后读取 DOM。它不表示网络请求完成,也不保证等待所有未来任务。

9. 常见误区

  • setup() 不是每次组件更新都会执行,render 才会反复执行。
  • 组件重新渲染不等于 DOM 全量重建。
  • 响应式数据变化后通常是异步批量更新,不是立即修改 DOM。
  • mounted 只表示首次挂载完成,不会在每次更新后执行。
  • render 阶段应保持纯粹,网络请求和订阅应放在合适的 effect / 生命周期中。

面试总结

Vue 遇到组件 VNode 时会创建组件实例并执行 setup,随后建立 render effect。首次执行 render 生成 VNode 并完成 mount;响应式依赖变化后,更新任务进入 scheduler,组件重新执行 render,生成新 VNode,再通过 patch 对比新旧 VNode,只更新必要的 DOM。组件实例、VNode 和真实 DOM 是三个不同层次。