组件

组件是 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>

从运行视角看,大致会发生这些事:

  1. 父组件创建组件 VNode
  2. 渲染器识别这是组件节点
  3. 创建组件实例
  4. 初始化 propsslots
  5. 执行 setup
  6. 拿到 render 函数
  7. 建立组件更新 effect
  8. 执行 render,得到子树 subTree
  9. 再把子树交给渲染器继续 patch

也就是说,组件并不是直接渲染成 DOM,而是先渲染成一棵子树,再由渲染器处理这棵子树

三、为什么组件实例这么重要

Vue 内部会为每个组件创建一个组件实例。

你可以把它理解成这个组件在运行时的“档案袋”,里面放着很多信息:

  • 当前组件类型
  • props
  • slots
  • setupState
  • 生命周期钩子
  • 渲染函数
  • 子树 subTree
  • 是否已经挂载

没有组件实例,框架就没法知道这个组件当前处于什么状态。

四、组件和普通元素最大的区别

普通元素节点比较直接:

  • div
  • span
  • p

渲染器看到后可以直接创建 DOM。

但组件节点不一样。渲染器看到组件时,不能直接生成 DOM,它必须先问:

  • 这个组件的 render 是什么
  • 它依赖哪些状态
  • 它最终会渲染出什么子树

所以组件会比普通元素多一层“实例化 + 执行 render”的过程。

五、props 本质上是什么

很多初学者把 props 理解成“父组件传给子组件的变量”,这个说法没错,但还不够。

从框架视角看,props 是组件的外部输入接口。

这意味着:

  • 组件内部要能读取它
  • 父组件更新时,子组件能感知变化
  • 它应该遵循单向数据流

所以 props 不只是一个值,更是一种组件边界。

六、slots 本质上是什么

slots 可以理解成“父组件把一段可渲染内容交给子组件”。

不要把插槽只看成“语法糖”,它本质上是一种更灵活的内容分发机制。

从运行角度看,插槽通常会表现为函数。

这也是为什么插槽很灵活:

  • 可以延迟执行
  • 可以在不同位置渲染
  • 可以拿到子组件暴露的数据

七、setup 为什么这么重要

在 Vue 3 中,setup 是组合式 API 的入口。

它的职责通常包括:

  • 初始化状态
  • 组织逻辑
  • 返回模板/渲染函数可用的数据

从源码视角看,setup 很关键,因为它是组件实例和用户逻辑接触的第一层。

八、组件更新到底是怎么发生的

很多人会问:

子组件为什么会重新渲染?

本质上还是那条老主线:

  1. 组件 render 在 effect 中执行
  2. render 里读取了响应式数据
  3. 响应式系统收集依赖
  4. 数据变化后触发组件 effect 重新执行
  5. 得到新的子树
  6. 渲染器对比新旧子树并更新页面

所以组件更新并不是“组件自己突然刷新了”,而是组件 render effect 被重新调度执行了。

九、从源码角度,组件最值得看哪些入口

如果你准备读 Vue 3 组件相关源码,推荐先抓这些函数:

1. createComponentInstance

负责创建组件实例。

2. setupComponent

负责初始化:

  • props
  • slots
  • 有状态组件的 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

只要你把这条主线理清,后面的 propsslots、生命周期、异步组件、KeepAlive 都会更容易串起来。

上次更新:
贡献者: Joe, joe