一、createElement 是干啥的?Vue 渲染流程里的关键角色
想学透 Vue2 的渲染逻辑,绕不开 createElement 这个核心函数,不少同学刚接触时觉得它抽象,其实把「它是干啥的、参数咋用、啥场景必须用、怎么避坑」这些问题拆明白,就能轻松掌握,下面用问答式结构,从基础到实战把 createElement 讲透~
createElement 理解成「虚拟 DOM 工厂」——它的任务是生成 VNode(虚拟节点),而 VNode 是 Vue 渲染真实 DOM 的“蓝图”。
Vue 的渲染流程是这样的:
写在 .vue 文件里的模板(<template>),会先被编译成 抽象语法树(AST),再进一步转换成 渲染函数,渲染函数里最核心的逻辑,就是调用 createElement 生成 VNode,Vue 会通过 patch 算法,把 VNode 转换成真实 DOM 并挂载到页面上。
举个极简例子,不用模板、纯用渲染函数写 Vue 组件:
new Vue({
el: '#app',
// 渲染函数接收 createElement 作为参数(通常简写成 h)
render(h) {
// h createElement 的别名,调用后生成 VNode
return h('div', { class: 'hello' }, '你好,Vue2!')
}
})
这段代码会在页面生成一个带 class="hello" 的 <div>是“你好,Vue2!”,能直观看到:createElement 是连接“逻辑”和“真实 DOM”的桥梁,没有它,Vue 根本不知道该怎么把 JS 逻辑变成页面元素~
createElement 的参数怎么玩明白?三个参数逐个拆
createElement(即 h 函数)接收 三个参数:h(tag, data, children),每个参数都有明确分工:
第一个参数:tag(要渲染的标签或组件)
- 可以是 字符串:
'div'、'button'这类 HTML 标签; - 可以是 组件选项对象:比如自己写的
MyComponent(需要先导入); - 甚至可以是 异步组件:
() => import('./MyComponent.vue')(实现路由懒加载的思路)。
第二个参数:data(数据对象,给节点传“配置”)
这是最容易懵的部分,因为它要装 DOM 属性、组件Props、样式、事件 等一堆配置,常见字段有这些:
attrs:给 DOM 元素加原生属性(title、placeholder);props:给组件传props(只有传自定义组件时有用);class:设置类名,支持 字符串、数组、对象(对象形式常用于动态切换类,{ active: isActive });style:设置内联样式,是个对象({ color: 'red', fontSize: '14px' });on:绑定事件({ click: handleClick },注意是给组件绑自定义事件;如果给原生 DOM 绑事件,逻辑一样);key:和列表渲染里的key作用一样,用于 diff 算法优化;- 还有
directives(自定义指令)、slot(插槽)等进阶用法…
第三个参数:children(子节点)
子节点支持 字符串、数组、VNode 三种形式:
- 字符串:直接当文本内容(
'点击我'); - 数组:里面可以嵌套
h函数调用(生成子 VNode),也能混字符串; - VNode:由
h函数生成的虚拟节点(比如嵌套一个子组件)。
学 createElement 只是为了替代模板?这些场景非它不可
很多同学疑惑:“模板写起来像 HTML,多直观?为啥非要学 createElement?” 其实模板是“声明式”的简化语法,而 createElement 代表的 渲染函数,是“命令式”的灵活武器,遇到这些场景,模板真搞不定:
动态组件切换(根据逻辑渲染不同组件)
比如做一个“Tab 切换”,点击不同标签显示不同组件,用模板得写一堆 v-if/v-else,但用渲染函数可以直接用 JS 逻辑控制:
render(h) {
let CurrentComponent
if (this.activeTab === 'user') {
CurrentComponent = UserCard // 假设 UserCard 是组件选项
} else {
CurrentComponent = ProductList
}
return h(CurrentComponent, { props: { data: this.data } })
}
模板的 <component :is="xxx"> 其实底层也是这么玩的,但自己写渲染函数能更灵活地加逻辑(比如切换时加动画、权限判断)。
复杂逻辑渲染(比如低代码/动态页面)
如果后端返回一个 JSON,描述页面该渲染哪些组件、传什么参数(类似低代码平台的玩法),模板根本“看不懂”这种动态配置,但渲染函数可以 循环解析 JSON,动态生成 VNode:
render(h) {
// 假设 this.pageConfig 是后端返回的配置,结构像:
// { components: [{ type: 'Button', props: { text: '点我' } }, { type: 'Input', props: { placeholder: '请输入' } }] }
return h('div', {}, this.pageConfig.components.map(config => {
// 根据 type 渲染不同组件
const Component = getComponentByType(config.type) // 自己实现的“组件映射表”
return h(Component, { props: config.props })
}))
}
性能敏感场景(减少编译开销)
模板在运行时需要先编译成渲染函数,再生成 VNode,如果某个组件 高频更新(比如实时刷新的仪表盘),可以直接写渲染函数,跳过“模板编译”这一步,让渲染更快。
封装高阶组件(给组件加通用逻辑)
比如做一个“权限控制组件”,要求只有登录用户能看到子组件,用渲染函数可以 包装子组件,动态修改它的渲染逻辑:
// 高阶组件:权限控制
function withAuth(WrappedComponent) {
return {
render(h) {
if (this.isAuthenticated) { // 假设 isAuthenticated 是登录状态
return h(WrappedComponent, { props: this.$attrs })
} else {
return h('div', '请登录后查看')
}
},
data() { return { isAuthenticated: false } }
}
}
// 使用时:
const AuthButton = withAuth(MyButton)
模板里的语法,在 createElement 里咋实现?
模板是渲染函数的“语法糖”,所有 v-if/v-for/:class/@click 最终都会被编译成 createElement 的调用,搞清楚对应关系,学渲染函数会更顺:
| 模板语法 | createElement 实现逻辑 |
|---|---|
v-if |
用 JS 的 if/else 判断 |
v-for |
用数组的 map 遍历生成子节点 |
:class |
放到 data.class(对象/数组形式) |
@click |
放到 data.on.click |
{{ msg }} |
子节点传字符串 this.msg |
举个综合例子,把模板转成渲染函数,感受一一对应关系:
模板写法:
<div
class="box"
:class="{ active: isActive }"
@click="handleClick"
>
<p v-if="showMsg">{{ msg }}</p>
<ul>
<li v-for="item in list" :key="item.id">{{ item.name }}</li>
</ul>
</div>
渲染函数写法:
render(h) {
return h('div', {
// 对应 :class
class: { box: true, active: this.isActive },
// 对应 @click
on: { click: this.handleClick }
}, [
// 对应 v-if="showMsg"
this.showMsg ? h('p', this.msg) : null,
// 对应 v-for
h('ul', this.list.map(item => {
return h('li', { key: item.id }, item.name)
}))
])
}
实际开发中,createElement 容易踩哪些坑?
学会用法后,还要避开这些“经典错误”,否则控制台报错能把人搞懵:
子节点格式错误:不是数组/文本
错误示例:
render(h) {
return h('div', {}, { msg: '我是错的' }) // 子节点传了对象,不是数组/文本
}
报错:Invalid VNode type: [object Object] (expected string, Object, or Array)
修正:子节点必须是 字符串 或 数组(数组里装 VNode/字符串):
render(h) {
return h('div', {}, '我是正确文本')
// 或者数组形式:[h('p', '正确'), '也可以混字符串']
}
数据对象属性写错:事件/Props 传不进去
错误示例(给组件传 Props 时写错字段):
// 假设 MyComponent 有个 props: { title: String }
render(h) {
return h(MyComponent, { attrs: { title: '错的' } }) // 应该用 props 而不是 attrs
}
结果:组件收不到 title,因为 attrs 是给 DOM 元素传原生属性的,给组件传 Props 要写在 data.props 里。
修正:
render(h) {
return h(MyComponent, { props: { title: '对的' } })
}
组件引用错误:传字符串当组件
错误示例(没导入组件,直接传字符串):
render(h) {
return h('MyComponent', { props: { data: {} } }) // MyComponent 是自定义组件,不是 HTML 标签
}
报错:Unknown custom element: <MyComponent>
修正:先导入组件,再传组件选项对象:
import MyComponent from './MyComponent.vue'
render(h) {
return h(MyComponent, { props: { data: {} } })
}
列表渲染忘加 key:diff 算法出问题
错误示例(循环生成子节点没写 key):
render(h) {
return h('ul', {}, this.list.map(item => {
return h('li', {}, item.name) // 没加 key
}))
}
隐患:Vue diff 时无法精准识别节点,导致更新时 DOM 操作冗余,甚至数据错乱。
修正:给每个子节点加 key(放在 data 对象里):
render(h) {
return h('ul', {}, this.list.map(item => {
return h('li', { key: item.id }, item.name)
}))
}
想写得更爽?试试 JSX 和 createElement 的结合
纯用 createElement 写复杂结构时,代码会很“碎”(全是嵌套的 h 函数),Vue2 支持 JSX(需要装 babel-plugin-transform-vue-jsx 插件),写法和 React JSX 很像,可读性直接起飞!
比如用 JSX 写之前的“权限控制+列表”例子:
render() {
return (
<div class="box" onClick={this.handleClick}>
{this.showMsg && <p>{this.msg}</p>}
<ul>
{this.list.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
</div>
)
}
对比纯 createElement 写法,JSX 更像 HTML,嵌套结构一目了然。本质上 JSX 还是会被编译成 createElement 调用,所以它是“语法糖”,让我们写得更爽~
从 Vue2 到 Vue3,createElement 有啥变化?
Vue3 里,createElement 还是叫 h 函数,但参数设计更灵活了:
- Vue2 的
h(tag, data, children)→ Vue3 的h(type, props?, children?); - Vue3 把
data里的attrs/props/on等字段合并到props里,不需要再严格区分; - Vue3 推荐用 组合式 API,但渲染函数的核心逻辑(生成 VNode)和 Vue2 一脉相承。
所以学透 Vue2 的 createElement,不仅能搞定当前项目,还能帮你理解 Vue 渲染原理,未来升级 Vue3 也更容易过渡~
掌握 createElement,打开 Vue 渲染的“黑箱”
createElement 是 Vue2 渲染机制的“心脏”——模板再好用,遇到动态逻辑、复杂封装、性能优化时,还是得靠它,把“参数怎么传、场景怎么选、坑怎么避”这几点吃透,再结合 JSX 写更简洁的渲染函数,你对 Vue 的理解会直接上一个台阶。
下次遇到“模板搞不定的需求”,别慌,想想 createElement 的用法,灵活用 JS 逻辑生成 VNode,你会发现:原来 Vue 的渲染还能这么玩!
版权声明
本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。
code前端网

