Vue 组件渲染流程
Vue 组件从创建到更新,经历了哪些阶段?
Vue 组件的一次更新可以简化为:
1. 创建组件实例
当 renderer 遇到组件 VNode 时,会创建组件实例。实例会保存:
- props;
- attrs;
- slots;
- emits;
- setup 状态;
- 生命周期和渲染相关信息。
Vue 3 中,组件的逻辑主要由 setup() 和 render function 组成;使用 <script setup> 时,编译器会把代码转换为 setup 相关实现。
2. setup 与 render
模板会被编译为 render function。首次渲染时,render effect 执行,读取 count,响应式系统因此建立依赖关系。
setup() 本身通常只在组件创建时执行一次;render 则可能执行多次。不要把每次渲染都要做的逻辑误放进 setup,也不要在 render 中执行副作用。
3. Mount:首次挂载
首次渲染时,Vue 会:
- 执行组件的 render function;
- 生成组件子树的 VNode;
- 递归创建元素、文本、子组件;
- 将真实 DOM 插入宿主容器;
- 执行 mounted 等生命周期。
简化流程:
多层嵌套组件的顺序
假设组件树是:
父组件必须先执行 setup 和 render,才能得到包含哪些子组件的 VNode。Renderer 随后按模板顺序深度优先递归 mount:
核心规律:
setup、beforeMount:父到子,先向下递归;- 同级兄弟组件:通常按模板出现顺序处理;
mounted:子到父,子树完成后再通知父组件;- 开始初始化是从外向内,完成回调是从内向外。
不要让业务正确性依赖兄弟组件的 mounted 先后,异步组件、Suspense、Teleport 和后续重构都可能改变完成时机。兄弟协作应由父组件、共享状态或明确的 Promise/事件调度。
DOM 挂载不等于浏览器已经绘制
Vue mount 完成表示真实 DOM 已经提交,但浏览器通常要等当前 JavaScript 任务和微任务结束后,才有机会执行样式计算、布局、绘制和合成:
因此 mounted 和 nextTick 只能说明 DOM 已经就绪,不能证明用户已经在屏幕上看到这一帧。
条件与异步组件
v-if="false":不创建组件实例,不执行setup和mounted;v-show="false":组件仍会完整挂载,只通过display: none隐藏;- 异步组件:渲染到该分支时才执行 loader,先显示占位或 loading,Chunk 完成后再创建真实组件实例;
- Vue 不会在
app.mount()时挂载项目中的所有页面,只会递归处理当前渲染分支产生的组件树。
4. Update:响应式更新
当 render 中读取的响应式数据发生变化时,响应式系统会通知组件的 render effect。组件更新任务进入 scheduler 队列,刷新时重新执行 render,得到新的 VNode。
组件重新执行 render 不代表所有 DOM 都重新创建。renderer 会比较新旧 VNode 的类型、props、子节点和 key,决定复用、更新、移动或删除。
5. 父子组件更新
父组件 render 产生新的子组件 VNode 后,Vue 会通过 props、slots 和 VNode 身份协调子组件:
- props 变化时,子组件会收到新的 props 并更新;
- props 没有变化时,子组件不一定需要重新渲染;
- 子组件自己的响应式状态变化,可以独立触发子组件更新;
- slot 内容由父组件生成,父组件更新可能导致 slot 内容重新计算。
组件更新和 DOM 更新不是同一个概念。组件 render 可能重新执行,但 patch 发现没有变化后,可以不修改 DOM。
6. 生命周期与渲染阶段
常见顺序可以简化为:
Composition API 中对应的生命周期注册方式包括 onMounted、onUpdated、onBeforeUnmount 等。需要读取挂载后的 DOM 时使用 onMounted;需要读取更新后的 DOM 时,通常使用 onUpdated 或等待 nextTick。
生命周期回调也应避免制造无限更新:在 updated 中再次无条件修改响应式状态,可能导致循环更新。
7. VNode、组件实例和 DOM 的关系
这三者不是同一个东西:
一个组件实例可以在多次更新中生成多个 VNode;一个组件也可能渲染多个 DOM 节点。VNode 是协调过程的中间描述,不是 DOM 本身。
8. nextTick 在流程中的位置
nextTick 等待 Vue 刷新当前更新队列,适合在状态修改后读取 DOM。它不表示网络请求完成,也不保证等待所有未来任务。
watch 默认是 flush: 'pre',回调可能早于当前组件 DOM 更新。需要在 DOM 提交后初始化依赖容器的第三方库时,可以等待 nextTick,或更明确地使用 flush: 'post':
如果不仅要等 DOM 提交,还希望先让 Loading 有机会绘制一帧,可以嵌套两次 requestAnimationFrame:
requestAnimationFrame 回调发生在当帧绘制之前。单次 rAF 里直接执行重任务,仍可能阻塞这一帧;嵌套第二次 rAF 会把重任务推迟到后续帧,让两次回调之间尽量留出一次绘制机会。这是工程调度技巧,不是严格的“屏幕绘制完成”通知。
9. 常见误区
setup()不是每次组件更新都会执行,render 才会反复执行。- 组件重新渲染不等于 DOM 全量重建。
- 响应式数据变化后通常是异步批量更新,不是立即修改 DOM。
mounted只表示首次挂载完成,不会在每次更新后执行。- render 阶段应保持纯粹,网络请求和订阅应放在合适的 effect / 生命周期中。
面试总结
Vue 遇到组件 VNode 时会创建组件实例并执行 setup,随后建立 render effect。首次执行 render 生成 VNode 并完成 mount;响应式依赖变化后,更新任务进入 scheduler,组件重新执行 render,生成新 VNode,再通过 patch 对比新旧 VNode,只更新必要的 DOM。组件实例、VNode 和真实 DOM 是三个不同层次。

