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 复制代码
施工队
相关推荐
光影少年1 小时前
前端css如何实现水平垂直居中?
前端·javascript·css
C澒1 小时前
SLDS 自营物流系统:Pickup 揽收全流程
前端·架构·系统架构·教育电商·交通物流
摸鱼的春哥2 小时前
把白领吓破防的2028预言,究竟讲了什么?
前端·javascript·后端
infiniteWei2 小时前
SKILL.md 触发机制与设计规范:避免“写了不触发”
java·前端·设计规范
慧一居士2 小时前
SVG图片介绍和使用
前端
Rysxt_2 小时前
Uniapp全局配置教程
前端·uniapp
阿珊和她的猫2 小时前
深入理解与使用 Cookie:Web 开发中的关键机制
前端·状态模式
用户547487596222 小时前
webpack代码分割
前端