组件
组件是 Vue 最核心的抽象之一。
如果只从使用角度看,组件像是“可复用的页面片段”;但从框架内部看,组件其实更像一个小型运行单元:
- 有自己的状态
- 有自己的输入输出
- 有自己的生命周期
- 有自己的渲染过程
先看这张图,会更容易理解:组件不是直接变成 DOM,而是先变成自己的子树,然后再进入渲染流程。
一、组件到底是什么
很多人会把组件理解成“一个 .vue 文件”,这只是表象。
更准确地说,组件是一套约定好的能力集合:
props:外部输入emit:向外通知slots:接收内容块setup/data:内部状态来源render/template:描述要渲染什么
所以组件并不只是 UI 片段,它本质上是“状态 + 视图 + 生命周期”的组合体。
二、一个组件从写出来到显示出来,经历了什么
以一个简单组件为例:
<script setup lang="ts">
const props = defineProps<{ title: string }>()
</script>
<template>
<div>{{ title }}</div>
</template>
从运行视角看,大致会发生这些事:
- 父组件创建组件
VNode - 渲染器识别这是组件节点
- 创建组件实例
- 初始化
props、slots - 执行
setup - 拿到
render函数 - 建立组件更新 effect
- 执行 render,得到子树
subTree - 再把子树交给渲染器继续 patch
也就是说,组件并不是直接渲染成 DOM,而是先渲染成一棵子树,再由渲染器处理这棵子树。
三、为什么组件实例这么重要
Vue 内部会为每个组件创建一个组件实例。
你可以把它理解成这个组件在运行时的“档案袋”,里面放着很多信息:
- 当前组件类型
propsslotssetupState- 生命周期钩子
- 渲染函数
- 子树
subTree - 是否已经挂载
没有组件实例,框架就没法知道这个组件当前处于什么状态。
四、组件和普通元素最大的区别
普通元素节点比较直接:
divspanp
渲染器看到后可以直接创建 DOM。
但组件节点不一样。渲染器看到组件时,不能直接生成 DOM,它必须先问:
- 这个组件的 render 是什么
- 它依赖哪些状态
- 它最终会渲染出什么子树
所以组件会比普通元素多一层“实例化 + 执行 render”的过程。
五、props 本质上是什么
很多初学者把 props 理解成“父组件传给子组件的变量”,这个说法没错,但还不够。
从框架视角看,props 是组件的外部输入接口。
这意味着:
- 组件内部要能读取它
- 父组件更新时,子组件能感知变化
- 它应该遵循单向数据流
所以 props 不只是一个值,更是一种组件边界。
六、slots 本质上是什么
slots 可以理解成“父组件把一段可渲染内容交给子组件”。
不要把插槽只看成“语法糖”,它本质上是一种更灵活的内容分发机制。
从运行角度看,插槽通常会表现为函数。
这也是为什么插槽很灵活:
- 可以延迟执行
- 可以在不同位置渲染
- 可以拿到子组件暴露的数据
七、setup 为什么这么重要
在 Vue 3 中,setup 是组合式 API 的入口。
它的职责通常包括:
- 初始化状态
- 组织逻辑
- 返回模板/渲染函数可用的数据
从源码视角看,setup 很关键,因为它是组件实例和用户逻辑接触的第一层。
八、组件更新到底是怎么发生的
很多人会问:
子组件为什么会重新渲染?
本质上还是那条老主线:
- 组件 render 在 effect 中执行
- render 里读取了响应式数据
- 响应式系统收集依赖
- 数据变化后触发组件 effect 重新执行
- 得到新的子树
- 渲染器对比新旧子树并更新页面
所以组件更新并不是“组件自己突然刷新了”,而是组件 render effect 被重新调度执行了。
九、从源码角度,组件最值得看哪些入口
如果你准备读 Vue 3 组件相关源码,推荐先抓这些函数:
1. createComponentInstance
负责创建组件实例。
2. setupComponent
负责初始化:
propsslots- 有状态组件的 setup 流程
3. setupStatefulComponent
负责执行组件的 setup。
4. finishComponentSetup
负责把 render 函数准备好。
5. setupRenderEffect
这是组件挂载和更新的关键,因为它把组件 render 放进了响应式 effect 中。
十、一个简化版源码脉络
你可以把组件主流程压缩成下面这样:
function mountComponent(initialVNode, container) {
const instance = createComponentInstance(initialVNode)
setupComponent(instance)
setupRenderEffect(instance, container)
}
function setupRenderEffect(instance, container) {
effect(() => {
if (!instance.isMounted) {
const subTree = instance.render()
patch(null, subTree, container)
instance.subTree = subTree
instance.isMounted = true
} else {
const nextTree = instance.render()
patch(instance.subTree, nextTree, container)
instance.subTree = nextTree
}
})
}
真实源码会处理更多细节,例如:
props更新slots更新- 生命周期钩子
- 调度器
- 异步组件
KeepAlive
但主线非常清晰:
- 创建实例
- 初始化实例
- 建立渲染 effect
- 首次挂载 / 后续更新
十一、为什么说组件是 Vue 架构的交汇点
因为很多能力最后都会汇总到组件上:
- 响应式
- 渲染器
- 编译器产出的 render 函数
- 生命周期
- 调度器
换句话说:
组件是 Vue 中“用户代码”和“框架运行时”真正相遇的地方。
十二、学组件原理时最容易卡住的点
1. 把组件当成“特殊元素”
组件确实会变成一棵树的一部分,但它不是普通元素。它有实例、有状态、有 render 过程。
2. 不理解 render 和组件更新的关系
组件更新不是直接改 DOM,而是:
- 重新执行 render
- 得到新的子树
- 再交给渲染器 patch
3. 一上来就看生命周期细节
生命周期当然重要,但建议先把组件主线看清:
- 创建实例
- 初始化
- render
- patch
总结
理解组件,最重要的是理解这条主线:
- 组件先被创建成实例
- 实例负责承载状态、输入输出和渲染函数
- render 返回子树
- 子树再交给渲染器处理
- 响应式变化会驱动组件重新执行 render
只要你把这条主线理清,后面的 props、slots、生命周期、异步组件、KeepAlive 都会更容易串起来。
