Vue2里的h函数到底是干啥的?怎么用?
很多刚接触Vue2的同学,看到代码里突然冒出个h函数,心里直犯嘀咕——这玩意儿到底是干啥的?和平时写的模板有啥关系?自己要不要学怎么用?今天就把h函数的来龙去脉、实用技巧拆明白,看完你就知道它在项目里咋发挥作用啦~
h函数是啥?和Vue模板有啥联系?
你可以把h函数理解成Vue在JS层面创建虚拟DOM(VNode)的“工具人”,平时写Vue模板(.vue文件里的),Vue最终会把模板编译成一堆h函数的调用;而h函数本身,其实是createElement
方法的别名,作用就是生成描述真实DOM结构的VNode对象。
举个直观例子:模板里写<div class="box">hello</div>
,Vue编译后大概长这样(简化版):
h('div', { class: 'box' }, 'hello')
所以不管是写模板还是用h函数,最终都是生成VNode给Vue渲染,模板是“声明式”写结构,h函数是“命令式”用JS拼结构——相当于给了你更底层的控制权。
h函数的基本用法咋掌握?
h函数的参数规则是「标签/组件 + 属性对象 + 子节点」,格式为h(tag, data, children)
,咱们拆分来看:
第一个参数:标签名或组件
- 如果是普通HTML标签,直接传字符串,比如
'div'
、'button'
; - 如果是Vue组件(不管全局/局部),传组件的选项对象(比如导入的
MyComponent
)或者已注册的组件名(字符串,前提是全局注册过)。
第二个参数:属性对象(data)
这个对象里装的是标签/组件的“配置”,常见的有这些:
- 样式类:
class: { active: true }
(对象)或class: ['red', 'bold']
(数组); - 内联样式:
style: { color: 'red', fontSize: '14px' }
; - DOM属性:
attrs: { id: 'my-div' }
(因为有些属性Vue不会自动绑定,比如自定义属性要放attrs
里); - 事件:
on:click: () => {}
(注意是on:
前缀,对应模板里的@click
); - 组件props:
props: { title: 'xxx' }
(给子组件传参); - 插槽:
scopedSlots: { default: () => h(...) }
(处理作用域插槽,稍复杂,实战再聊)。
第三个参数:子节点(children)
子节点可以是这几种形式:
- 字符串:比如
'这是文本'
,对应模板里的文本节点; - 数组:里面装多个h函数调用(嵌套子元素),比如
[h('span', {}, 'A'), h('span', {}, 'B')]
; - 单个h函数:如果只有一个子元素,直接传h函数调用。
举个完整例子,用h函数写一个带按钮和文本的结构:
h('div', { class: 'container' }, [ h('p', {}, '这是一段说明文字'), h('button', { on: { click: () => alert('点我') } }, '点我试试') ])
这段代码渲染后,和模板写的:
<div class="container"> <p>这是一段说明文字</p> <button @click="() => alert('点我')">点我试试</button> </div>
效果完全一样~
为啥不用模板非得用h函数?场景在哪?
模板写起来简单直观,那h函数存在的意义是啥?这得看“灵活性”需求,这些场景里h函数更顺手:
动态渲染结构(模板难写的逻辑)
比如做一个“动态标签组件”:根据传入的level
参数(1 - 6)渲染h1~h6
标签,用模板的话,得写6个v-if
分支,特别冗余;用h函数就一行解决:
render(h) { return h(`h${this.level}`, {}, this.content) }
再比如表格的列数、列内容完全动态(从后端接口拿配置),用h函数循环生成<th>``<td>
,比模板里嵌套v-for
更灵活。
配合JSX语法(写React味的Vue代码)
很多人喜欢JSX的“JS和HTML混写”风格,Vue里用JSX的话,编译后其实就是h函数调用,比如写:
<div class={active ? 'active' : ''}>{text}</div>
Vue会把它转成h('div', { class: active ? 'active' : '' }, text)
,所以学h函数,相当于给JSX语法打基础~
封装通用组件/库(底层逻辑复用)
如果你要写一个UI库(比如自己封装表格、弹窗),很多结构需要动态生成,用h函数能更精细地控制渲染逻辑,比如封装一个“弹窗组件”,头部、内容、底部按钮都要支持自定义,用h函数拼接不同部分的VNode,比模板里写一堆slot
更灵活。
h函数和渲染函数、VNode啥关系?
这仨概念经常一起出现,得理清楚:
- VNode(虚拟DOM节点):是Vue里描述真实DOM的“对象模型”,里面存了标签名、属性、子节点等信息,真实DOM的创建、更新、销毁,都是靠VNode的对比来实现的。
- 渲染函数(render function):Vue组件里的
render
选项,作用是返回VNode。export default { render(h) { return h('div', {}, 'Hello') // 返回一个VNode } }
- h函数:是创建VNode的“工具”,渲染函数里调用h函数,生成VNode给Vue处理,所以三者的关系是:
h函数 → 生成VNode → 渲染函数返回VNode → Vue根据VNode渲染真实DOM
。
实战中怎么用h函数解决问题?举个例子
光说不练假把式,咱们拿“递归生成树形结构”这个常见需求,看看h函数咋用:
需求:做一个树形组件,每个节点可以展开/折叠,子节点结构和父节点一样(典型的递归场景)。
步骤1:定义组件结构
组件接收一个treeData
数组,每个节点有name
和children
(子节点数组)。
步骤2:用h函数递归渲染
核心是在渲染函数里,判断当前节点是否有子节点,如果有,递归调用h函数生成子节点的VNode:
export default { name: 'Tree', props: { treeData: { type: Array, default: () => [] } }, render(h) { // 递归函数:接收节点数据,返回对应的VNode const renderNode = (node) => { // 渲染当前节点的标签 const nodeVNode = h('div', { class: 'tree-node' }, node.name) // 如果有子节点,递归渲染子节点 if (node.children && node.children.length) { const childrenVNode = node.children.map(child => renderNode(child)) return h('div', { class: 'tree-item' }, [nodeVNode, ...childrenVNode]) } return nodeVNode } // 根节点循环渲染所有顶级节点 const rootVNode = this.treeData.map(node => renderNode(node)) return h('div', { class: 'tree' }, rootVNode) } }
这段代码里,renderNode
函数不断递归调用自己,用h函数生成每个节点的VNode,如果用模板写递归组件,得配合<component :is="..." />
和slot
,反而没这么直接~
用h函数容易踩哪些坑?咋避?
掌握用法后,这些“雷区”得小心:
事件绑定写错格式
模板里用@click
,h函数里要写成on:click
(注意是小写!),比如正确写法:
h('button', { on: { click: this.handleClick } }, '点我')
如果写成onClick
(驼峰),Vue识别不到,事件就绑定失败!
子节点格式不对
h函数的第三个参数(子节点)必须是字符串、数组(装h函数/VNode)、或单个h函数,如果传了对象、数字(除了字符串转的),会报错,比如想渲染数字123,得转成字符串:
// 错误:直接传数字 h('div', {}, 123) // 正确:转成字符串 h('div', {}, '123')
组件引用出错
如果用组件当第一个参数,得注意:
- 局部组件:必须先
import
再传入,比如import MyComp from './MyComp.vue'
,然后h(MyComp, ...)
; - 全局组件:可以传字符串名称,比如
h('MyGlobalComp', ...)
(前提是用Vue.component
全局注册过)。
如果局部组件传字符串,Vue找不到组件,会渲染成空标签~
样式绑定踩坑
class和style的格式要和模板里一致:
- class可以是对象(
{ active: true }
)或数组(['red', 'bold']
); - style可以是对象(
{ color: 'red' }
)或数组([{ color: 'red' }, { fontSize: '14px' }]
)。
如果写成class: 'red bold'
(字符串),虽然能生效,但动态切换类名时不灵活,建议用对象/数组~
现在再回头看h函数,是不是觉得它既“底层”又“灵活”?模板适合快速写静态或逻辑简单的结构,h函数则是应对动态、复杂渲染逻辑的利器,下次遇到“模板写起来绕”的场景,不妨试试h函数,感受下JS层面操控虚拟DOM的自由~要是你在学习过程中还有啥疑问,比如JSX和h函数咋配合,或者更复杂的插槽用法,评论区喊我,咱们再拆解!
版权声明
本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。
发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。