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 复制代码
施工队
相关推荐
IT_陈寒2 小时前
React开发者都在偷偷用的5个性能优化黑科技,你知道几个?
前端·人工智能·后端
还是大剑师兰特2 小时前
Vue3 前端专属配置(VSCode settings.json + .prettierrc)
前端·vscode·json
前端小趴菜052 小时前
vue3项目优化方案
前端·javascript·vue.js
Mr_Swilder2 小时前
WebGPU 基础 (WebGPU Fundamentals)
前端
张3蜂2 小时前
HTML5语义化标签:现代网页的骨架与灵魂
前端·html·html5
悟空瞎说3 小时前
我用 PixiJS 撸了个圆桌会议选座系统,从 0 到 1 踩坑全复盘
前端
码云之上3 小时前
从 SPA 到全栈:AI 时代的前端架构升级实践
前端·架构·ai编程
Irene19913 小时前
对比总结:Vue3中的 watch 和 Pinia中的 $subscribe
vue.js·pinia·watch·subscribe
小陈同学呦3 小时前
关于如何使用CI/CD做自动化部署
前端·后端
前端Ah3 小时前
记 华为鸿蒙机型小程序使用uni.createInnerAudioContext() 播放音频播放两次的问题
前端