createElement → VNode 是怎么创建的

createElement → VNode 是怎么创建的

一、createElement 在哪里?

真正核心函数在:src/core/vdom/create-element.js

initRender 里安装的是:

js 复制代码
vm._c = (...) => createElement(vm, ...)
vm.$createElement = (...) => createElement(vm, ...)

二、createElement 的函数签名

简化版:

js 复制代码
function createElement(
  context,
  tag,
  data,
  children,
  normalizationType,
  alwaysNormalize
)

参数解释:

参数 作用
context 当前组件实例
tag 标签名 或 组件
data attrs、props、on 等
children 子节点
normalizationType 子节点规范化类型
alwaysNormalize 是否强制标准化

三、createElement 内部流程(核心逻辑)

流程图看全貌:

Plain 复制代码
createElement
   │
   ├─ 参数兼容处理
   │
   ├─ children 规范化
   │
   ├─ 判断 tag 类型
   │     ├─ 字符串
   │     │     ├─ 原生标签 → 创建普通 VNode
   │     │     └─ 组件名   → 创建组件 VNode
   │     │
   │     └─ 构造函数 → 创建组件 VNode
   │
   └─ 返回 VNode

四、第一步:参数处理

Vue 允许写:

js 复制代码
h('div', [child])

也允许:

js 复制代码
h('div', data, children)

内部会做:

js 复制代码
if (Array.isArray(data) || isPrimitive(data)) {
  children = data
  data = undefined
}

参数兼容处理。

五、第二步:children 规范化

Vue 内部要求:

children 必须是 VNode 数组

但可能写:

js 复制代码
h('div', 'hello')

所以要统一处理:

js 复制代码
children = normalizeChildren(children)

规范化会做:

  • 把字符串变成文本 VNode
  • 把嵌套数组拍平
  • 合并相邻文本节点

六、第三步:判断 tag 类型(关键)

js 复制代码
if (typeof tag === 'string') {

分三种情况:

1️⃣ 原生标签

js 复制代码
h('div')

判断方式:

js 复制代码
if (config.isReservedTag(tag))

例如:

  • div
  • span
  • p

会创建普通 VNode:

js 复制代码
return new VNode(tag, data, children, ...)

2️⃣ 组件

js 复制代码
h(MyComponent)

或者:

js 复制代码
h('my-component')

Vue 会通过:

js 复制代码
resolveAsset(context.$options, 'components', tag)

找到组件构造函数。

然后进入:

js 复制代码
createComponent(...)

🔥 createComponent 是关键

这个函数会创建一个:

js 复制代码
componentVNode

并在 data.hook 上挂载:

js 复制代码
init
prepatch
insert
destroy

这些钩子会在 patch 阶段触发。

七、VNode 长什么样?

VNode 本质是一个类:

js 复制代码
class VNode {
  constructor(
    tag,
    data,
    children,
    text,
    elm,
    context,
    componentOptions
  )
}

一个普通 VNode 结构大概是:

js 复制代码
{
  tag: 'div',
  data: { attrs: {}, on: {} },
  children: [VNode, VNode],
  text: undefined,
  elm: undefined,
  context: vm,
  componentOptions: undefined
}

组件 VNode 则多了:

js 复制代码
componentOptions: {
  Ctor,
  propsData,
  listeners,
  children
}

八、普通元素 vs 组件的区别

类型 patch 时行为
普通 VNode 创建真实 DOM
组件 VNode 创建组件实例

这就是:

Plain 复制代码
VNode 只是描述
patch 才是执行

九、完整链路串起来

Plain 复制代码
模板
  ↓
编译成 render 函数
  ↓
render 调用 _c
  ↓
createElement
  ↓
创建 VNode
  ↓
patch
  ↓
根据 VNode 类型
    ├─ 创建 DOM
    └─ 创建组件实例

十、举个完整例子

模板:

vue 复制代码
<div>
  <child />
</div>

编译后:

js 复制代码
render() {
  return _c('div', [
    _c('child')
  ])
}

执行顺序:

  1. createElement('div') → 普通 VNode
  2. createElement('child') → 组件 VNode
  3. patch
  4. 创建 div DOM
  5. 发现 child 是组件 → new Child()
  6. 子组件再 render

十一、核心理解

一句话:

createElement 只负责"描述",patch 才负责"执行"。

VNode 是:

Plain 复制代码
DOM 的蓝图

patch 是:

Plain 复制代码
施工队
相关推荐
前端技术几秒前
ArkTS第三章:声明式UI开发实战
java·前端·人工智能·python·华为·鸿蒙
码小瑞4 分钟前
画布文字在不同缩放屏幕上的归一化
前端
神の愛5 分钟前
java日志功能
java·开发语言·前端
小李子呢02116 分钟前
前端八股(1)--Promise 常用方法有哪些?和async和await的区别
前端
喵个咪11 分钟前
Go 语言 CMS 横评:风行 GoWind 对比传统 PHP/Java CMS 核心优势
前端·后端·cms
落魄江湖行14 分钟前
入门篇八 Nuxt4页面元信息与 SEO:让搜索引擎爱上你的网站
前端·typescript·seo·nuxt4
╰つ栺尖篴夢ゞ16 分钟前
Web之深入解析Cookie的安全防御与跨域实践
前端·安全·存储·cookie·跨域
木斯佳19 分钟前
前端八股文面经大全:腾讯前端一面(2026-04-04)·深度解析
前端·ai·鉴权·monorepo
code_Bo22 分钟前
kiro生成小程序商业案例
前端·微信小程序·小程序·云开发
yellowbuff23 分钟前
为什么你的 0.01 秒倒计时看起来一卡一卡的?
前端