Code前端首页关于Code前端联系我们

Vue3.0里的h函数到底怎么用?能解决哪些开发问题?

terry 2小时前 阅读数 9 #SEO
文章标签 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,第三个参数是组件的子节点(相当于模板里的插槽内容)。

处理带插槽的组件

要是组件有命名插槽(defaultfooter 插槽),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,页面没变化。

避坑:refreactive 让数据变成响应式。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前端网发表,如需转载,请注明页面地址。

热门