Vue3.0里的h函数到底怎么用?能解决哪些开发问题?
h函数在Vue3里扮演啥角色?
要理解h函数,得先从Vue的渲染逻辑说起,Vue要把我们写的界面变成浏览器能显示的真实DOM,中间得经过虚拟DOM(VNode)这一步——虚拟DOM是对真实DOM的“描述对象”,能帮Vue高效更新页面,而 h函数就是创建虚拟DOM节点的核心工具,它接收参数生成VNode,供Vue的渲染器转换成真实DOM。
举个简单例子:用h函数写 h('div', { class: 'box' }, '这是内容'),就能生成对应虚拟DOM节点,后续Vue会把它变成 <div class="box">这是内容</div> 这样的真实DOM。
h函数的参数结构很清晰:第一个参数是标签名/组件('div'、自定义组件 MyComponent);第二个是属性对象(包含class、style、事件绑定等);第三个是子节点(可以是字符串、数组、甚至另一个h函数调用),理解这三个参数,就掌握了h函数的基础用法。
怎么用h函数创建不同类型的节点?
实际开发中,我们要渲染的节点类型五花八门,h函数都能灵活应对:
创建普通HTML元素
比如做个带点击事件的按钮,用h函数可以这样写:
import { h } from 'vue'
const btn = h('button', {
onClick: () => alert('点我弹出内容')
}, '点击按钮')
这段代码和模板里写 <button @click="..."> 逻辑一致,只是用纯JS逻辑组织节点。
创建自定义组件
如果要渲染自己写的 MyCard 组件,得先导入组件,再把组件对象传给h函数(注意不是字符串!):
import MyCard from './MyCard.vue'
const card = h(MyCard, { '卡片标题',
desc: '卡片描述'
}, [
h('p', '卡片里的额外内容') // 子节点可以是h函数生成的元素
])
这里第二个参数是传给 MyCard 的props,第三个参数是组件的子节点(相当于模板里的插槽内容)。
处理带插槽的组件
要是组件有命名插槽(default 和 footer 插槽),h函数的第三个参数得用对象区分插槽名:
h(MyLayout, null, {
default: () => h('section', '主体内容'),
footer: () => h('div', '页脚信息')
})
这样 MyLayout 组件里的 <slot name="footer" /> 就能渲染出对应的“页脚信息”了。
和模板语法比,h函数适合哪些开发场景?
模板语法(.vue 里的 <template>)写静态结构又快又直观,但遇到高度动态、逻辑复杂的场景,h函数的优势会更明显:
数据驱动的动态UI
比如后台管理系统的动态表单:表单字段由后端接口返回(字段类型可能是输入框、下拉框、日期选择器等),这时候用h函数循环数据,根据字段类型动态生成节点特别方便:
const formItems = [
{ type: 'input', label: '用户名' },
{ type: 'select', label: '性别', options: ['男', '女'] }
]
// 用h函数循环生成表单组件
const formVNodes = formItems.map(item => {
if (item.type === 'input') {
return h(MyInput, { label: item.label })
} else if (item.type === 'select') {
return h(MySelect, { label: item.label, options: item.options })
}
})
要是用模板写,得嵌套一堆 v-if/v-for,代码会很臃肿。
可视化编辑器、低代码平台
这类工具需要根据用户操作动态生成渲染逻辑(比如拖拽组件后,实时生成对应的UI代码),h函数能通过纯JS逻辑拼接VNode,完美适配这种“动态拼接界面”的需求。
函数式组件封装
函数式组件(没有状态的组件)不需要写模板,只能用h函数返回VNode,比如封装一个“根据权限显示内容”的函数式组件:
export const AuthButton = (props, { slots }) => {
if (checkPermission(props.role)) {
return h('button', null, slots.default())
}
return h('span', '无权限')
}
这种纯逻辑驱动的组件,h函数是唯一选择。
h函数和render函数、JSX有啥联系?
这三者经常一起出现,理清关系能少踩很多坑:
h函数是render函数的“画笔”
Vue组件的 render 选项,本质是个返回VNode的函数,而h函数就是这个函数里的“画笔”——用h生成VNode,给render函数返回:
export default {
render() {
return h('div', '这是render函数里生成的内容')
}
}
所以学h函数,是学render函数的基础。
JSX是h函数的“语法糖”
写JSX的时候(<div>Hello</div>),Babel会把它自动转成h函数调用(h('div', 'Hello')),所以JSX本质是h函数的更友好写法,学懂h函数,看JSX代码时就知道背后发生了什么。
举个JSX和h函数的等价例子:
// JSX写法
const element = <MyComp title="hi">{/* 插槽内容 */}</MyComp>
// 等价的h函数写法
const element = h(MyComp, { title: 'hi' }, {
default: () => h('p', '插槽内容')
})
理解这种对应关系,以后用JSX开发时,遇到复杂逻辑也能灵活改用h函数调试。
用h函数开发时容易踩哪些坑?怎么避?
h函数灵活但易出错,这些“避坑指南”得记好:
参数顺序搞反,导致属性/子节点丢失
h函数的参数是 h(标签/组件, 属性对象, 子节点),但新手容易把属性和子节点写反,比如想给div加class并传内容,写成 h('div', '内容', { class: 'box' }) —— 这就错了!属性对象和子节点位置互换,会导致class没生效、内容也不对。
避坑: 严格记牢参数顺序:标签→属性→子节点。
渲染自定义组件时,传了字符串而非组件对象
想渲染 MyDialog 组件,结果写成 h('MyDialog', { ... })(第一个参数是字符串),这时候Vue会把它当HTML标签(但浏览器没有 <MyDialog> 标签),直接报错。
避坑: 渲染自定义组件时,先 import 组件,再把组件对象传给h函数第一个参数。
多子节点没放数组里,导致只渲染最后一个
比如想渲染两个span:h('div', null, h('span', '1'), h('span', '2')) —— 这样写只会显示第二个span,因为h函数的子节点如果是多个单独的h调用,会被当成“多个参数”,而h函数只认数组形式的子节点。
避坑: 多个子节点必须用数组包裹,h('div', null, [h('span', '1'), h('span', '2')])。
响应式数据更新,视图却不刷新
在 setup 里用h函数时,如果数据不是响应式的,改了数据视图不会更新,比如直接写 let count = 0,然后用h函数渲染 count,点按钮改count,页面没变化。
避坑: 用 ref 或 reactive 让数据变成响应式。const count = ref(0),h函数里用 count.value,这样数据变化时Vue能检测到并更新视图。
Vue3的h函数和Vue2比有啥升级?
如果之前用过Vue2,会发现Vue3的h函数更“聪明”了:
- 模块化拆分:Vue2里h函数挂在Vue构造函数上(
Vue.h),Vue3把它单独放到@vue/runtime-core包,更适合库开发和Tree-shaking(减少打包体积)。 - 参数更灵活:Vue3支持用
null当标签名创建Fragment(多个根节点),h(null, [h('div'), h('p')]),而Vue2要求组件必须只有一个根节点。 - TS支持更友好:Vue3对h函数的类型定义做了强化,传错组件、属性类型不匹配时,TS能直接报错提醒,写代码更安心。
h函数是Vue3虚拟DOM体系的核心工具,看似简单却能解决模板搞不定的复杂场景,从基础的节点创建,到动态UI、函数式组件、JSX原理,掌握h函数能让你对Vue的渲染逻辑理解更透彻,开发时也更游刃有余,要是你在项目里遇到“模板写着太别扭”的场景,不妨试试h函数,说不定能打开新世界的大门~
版权声明
本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。
code前端网

