组件实例仅管理自身对应的vnode子树,通过_vnode属性持有当前根vnode,由渲染器统一调度diff与挂载;插槽使父级vnode可跨实例流动并嵌入子树。

组件实例并不“管理”整棵 VNode 树,而是只负责自身对应的那一小块 VNode 子树——它通过 创建、持有、更新属于自己的 VNode,并在挂载/更新/卸载时与渲染器协同,让这棵子树被正确映射为真实 DOM。
每个组件实例对应一个独立的 VNode 节点
当父组件的 render 函数执行时,遇到自定义组件(如 <userlist></userlist>),会调用 createComponent 生成一个组件类型的 VNode。这个 VNode 的 type 是组件构造函数(比如 UserList),componentOptions 中存着 props、slots 等信息。它本身不包含子节点结构,只是一个“占位符”,但明确指向该组件实例应负责的渲染范围。
- 这个 VNode 在 patch 阶段会被识别为组件节点,进而触发子组件实例化
- 子组件实例创建后,会用自己的
render或模板生成自己的 VNode 子树(即它的“内部视图”) - 父组件的 VNode 树中,只保留对这个子 VNode 的引用,不深入其 children
组件实例通过 _vnode 属性持有当前渲染的 VNode
每个组件实例(vm 或 instance)内部都有一个 _vnode(Vue 2)或 vnode(Vue 3)属性,它始终指向**本次渲染所生成的那个根 VNode**。这个引用是动态更新的:
- 首次挂载时,
_vnode指向由render()返回的根 VNode - 响应式更新后重新渲染,新 VNode 生成并赋值给
_vnode,旧 VNode 进入 diff 流程 -
_vnode.el则指向对应的真实 DOM 元素,由渲染器在挂载后设置
组件实例不直接操作整棵树,而是响应渲染器调度
VNode 树的组织和比对由渲染器(renderer)统一控制,组件实例只提供“内容生产者”的角色:
- 它不遍历或修改父/兄弟的 VNode,也不主动触发其他组件的更新
- 当自身响应式状态变化,会触发
effect调度器,最终调用renderer.patch对自己的_vnode和旧 VNode 做 diff - 若使用
KeepAlive,组件实例和它的_vnode会被缓存,跳过卸载,但依然由渲染器决定何时激活或停用
插槽(Slots)是跨实例 VNode 流动的关键桥梁
父组件向子组件传递内容,本质是把一段 VNode(来自父的 $slots)作为 prop 交给子组件。子组件在自己的 render 函数中可以显式调用这些 slot 函数,将父级 VNode 插入到自己的 VNode 子树中:
-
this.$slots.default?.()返回的是父作用域生成的 VNode 数组 - 这些 VNode 的
parent指针仍指向父组件实例,但它们的挂载位置属于子组件的 DOM 范围 - 因此,同一段 VNode 可能被多个组件实例“看到”,但只由一个组件实例负责最终挂载
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











