Vue中VNode的children可为字符串/数字(转文本节点)、数组(多子节点,自动规范化)、单个VNode对象(简化单子节点)、或函数(作用域插槽,渲染时执行)。

Vue.js 中的 VNode(虚拟节点)的 children 属性,决定了子节点如何组织与渲染。它不是固定为某一种类型,而是根据模板写法、JSX 写法或手动创建 VNode 的方式,呈现多种格式——理解这些格式及其对应的渲染逻辑,对调试、自定义渲染器、高阶组件或函数式组件至关重要。
字符串/数字等基础类型:作为文本子节点
当 children 是字符串、数字、布尔值(true/false 会被忽略)、null 或 undefined 时,Vue 将其视为纯文本内容,生成一个没有标签的文本 VNode。
例如:
```jsh('div', 'Hello') // children = 'Hello'
h('span', 123) // children = 123
```
渲染结果为:<div>Hello</div>、<span>123</span>。注意:false、null、undefined、0(数字零会被保留)中,仅 false 和 undefined / null(在非数组上下文中)会被跳过;0 和空字符串 '' 仍会渲染为文本节点。
立即学习“前端免费学习笔记(深入)”;
数组:标准的多子节点结构
最常见也最明确的形式是数组,每个元素是一个 VNode、字符串、或可被规范化为 VNode 的值(如组件对象、异步组件等)。
例如:
```jsh('ul', [
h('li', 'Item 1'),
h('li', 'Item 2'),
'Plain text' // 自动转为文本 VNode
])
```
Vue 会对数组做规范化处理(通过 normalizeChildren),将非 VNode 类型(如字符串、数字)包装成文本 VNode,并过滤掉无效值(undefined、null、false)。该数组最终成为 children 的稳定结构,供 patch 过程逐个比对更新。
关键点:
Vue 3 组合式 API 与 最佳实践,涵盖代码组织、useXxx 模式、组合式函数设计、Store 集成、响应式优化及功能抽取。
- 数组内可混合 VNode、字符串、数字、甚至嵌套数组(但需注意:嵌套数组不会自动扁平化,需手动
flat()或用h多层构造) - 响应式数组变化(如 push/splice)会触发子节点重渲染,前提是父组件追踪了该数组依赖(通常在
setup()中返回或作为响应式对象属性)
单个 VNode 对象:简化单子节点场景
当组件或元素只有一个子节点时,children 可直接设为一个 VNode 对象,无需包裹数组。
例如:
```jsh('div', h('p', 'Only one child'))
// children = 一个 VNode 实例
```
这种写法等价于 h('div', [h('p', 'Only one child')]),但更轻量。Vue 内部在 patch 前会统一规范化:若 children 是对象且有 type 属性(即符合 VNode 形状),就视作单子节点;否则按数组或文本处理。
注意:不能传入普通对象(如 { msg: 'x' }),必须是合法 VNode(由 h() 创建或手动构造)。
函数(作用域插槽/动态子节点):延迟求值的 children
在某些高级场景(如自定义组件接收插槽、或手动实现作用域插槽),children 可以是一个函数。该函数在渲染时执行,返回实际的子节点(可以是 VNode、数组或 null)。
例如:
```jsh(MyComponent, {
// 插槽作为函数传入
default: () => [h('strong', 'Scoped'), h('em', 'content')]
})
```
此时 children 是一个函数,Vue 渲染器会调用它获取真实子 VNode 列表。这支持响应式数据驱动的动态结构,也用于实现 v-for + 插槽、或条件性插槽内容。
要点:
- 函数本身不参与 diff,每次 render 都重新执行,返回值才进入子节点比对流程
- 函数内可访问闭包变量或响应式状态,实现“带作用域”的动态渲染
- 不推荐在非插槽场景滥用此形式,易导致性能问题(无缓存、重复执行)
不复杂但容易忽略:VNode 的 children 格式直接影响规范化路径和 diff 策略。正确使用能避免 “Invalid VNode type” 报错、文本节点意外丢失、或子节点不更新等问题。

















