Vue3 h函数手写渲染函数替代模板的场景
Vue 3 的 h 函数(即 hyperscript)提供了一种直接通过 JavaScript 代码创建虚拟节点(VNode)的方式。在某些特定场景下,放弃模板转而使用 h 函数手写渲染函数,能显著提升代码的灵活性和控制力。
理解 h 函数的核心参数
在使用 h 函数前,必须明确其接收的三个核心参数。
$$ h(type, props, children) $$
其中 type 可以是 HTML 标签字符串、组件或异步组件;props 是传递的属性、事件、类等;children 是子节点,可以是字符串、数组或对象。掌握了这个公式,就掌握了渲染函数的基石。
场景一:动态标签的底层实现
当组件的根元素标签需要根据外部条件动态变化时,使用 h 函数比模板中的 <component :is="..."> 更加底层且高效。
-
定义 动态标签的 Props。
在script setup中声明一个接收标签名的 prop。 -
编写 渲染逻辑。
判断 传入的标签类型,直接将其作为h函数的第一个参数。
import { h } from 'vue'
const DynamicTag = (props, { slots }) => {
// 直接将 props.tag 作为第一个参数传入 h 函数
return h(props.tag, { class: 'dynamic-wrapper' }, slots.default ? slots.default() : '默认内容')
}
- 调用 该组件。
在父组件中传入不同的标签名,组件会自动渲染为对应的 HTML 元素。
场景二:构建高阶组件(HOC)或逻辑包装器
若需创建一个不改变原有组件逻辑,但额外添加功能(如权限控制、加载状态、数据埋点)的包装器,h 函数是最佳选择。
-
创建 包装器组件。
新建一个WithPermission.vue文件。 -
设计 权限拦截逻辑。
在setup函数中,检查 当前用户的权限标识。 -
执行 条件渲染。
如果权限通过,渲染 传入的插槽内容;否则,渲染 一个无权限提示组件或null。
import { h } from 'vue'
const WithPermission = (props, { slots }) => {
const hasPermission = true // 假设这是权限校验逻辑
if (!hasPermission) {
// 权限不足时渲染空或提示
return h('div', { style: 'color: red' }, '无权访问')
}
// 权限足够时,渲染默认插槽内容
// 注意:slots.default() 返回的是 VNode 数组
return h('div', { class: 'permission-wrapper' }, slots.default ? slots.default() : [])
}
- 使用 包装器。
将需要受保护的组件包裹在WithPermission中。
<template>
<WithPermission>
<button>删除用户</button>
</WithPermission>
</template>
<script setup>
import WithPermission from './WithPermission.vue'
</script>
场景三:处理极度复杂的循环或列表渲染
当 v-for 指令配合 v-if 变得极其复杂,或者列表结构需要根据数据深度动态变化时,模板的可读性会急剧下降。此时,使用 JavaScript 的 map 和 h 函数组合会更加清晰。
-
准备 多层级数据源。
假设有一个包含多级嵌套菜单的树形结构数据。 -
编写 递归渲染函数。
定义 一个内部函数,接收当前层级的数据,并返回h生成的 VNode。
const renderTree = (nodes) => {
if (!nodes || !nodes.length) return null
return h(
'ul',
{ class: 'tree-list' },
nodes.map(node => {
// 递归调用 renderTree 处理子节点
const childrenVNodes = renderTree(node.children)
return h(
'li',
{ key: node.id },
[
h('span', { class: 'node-label' }, node.name),
childrenVNodes
]
)
})
)
}
const TreeView = (props) => {
return renderTree(props.data)
}
- 传入 数据。
在父组件中直接将树形数据传递给TreeView组件。
场景四:JSX 的替代方案与原生优势
虽然 Vue 3 支持 JSX,但 JSX 需要额外的编译配置。h 函数作为 Vue 的原生 API,不需要任何预处理器即可直接在 .js 或 .ts 文件中运行。
-
处理 事件修饰符。
在模板中我们习惯使用.stop、.prevent等修饰符,但在h函数中,需手动处理事件对象。 -
编写 手动阻止默认行为代码。
import { h } from 'vue'
const MyForm = () => {
return h('form', {
onSubmit: (event) => {
event.preventDefault() // 手动调用 preventDefault
console.log('表单提交')
}
}, [
h('button', { type: 'submit' }, '提交')
])
}
- 绑定 多个类名或样式。
使用对象语法或数组语法直接作为props传入h函数,这与模板中的:class或:style行为一致。
h('div', {
class: [
'base-class',
{
'active': isActive,
'disabled': isDisabled
}
],
style: {
color: 'red',
fontSize: '16px'
}
}, '内容文本')
场景五:函数式组件的极致简化
在 Vue 3 中,函数式组件被定义为纯函数。这类组件没有状态 (data),没有实例 (this),只接收 props 和 context。对于纯展示型的 UI 组件(如按钮、标签、徽章),使用 h 函数编写性能开销最小。
-
声明 组件为一个常量。
直接将箭头函数赋值给常量。 -
返回 虚拟 DOM。
在函数体内直接return h(...)的结果。
// SmartButton.js
import { h } from 'vue'
export default (props) => {
// 根据 type 属性决定颜色
const colorMap = {
primary: '#007bff',
danger: '#dc3545',
success: '#28a745'
}
return h(
'button',
{
style: {
backgroundColor: colorMap[props.type] || '#ccc',
color: '#fff',
padding: '10px 20px',
border: 'none',
borderRadius: '4px'
},
onClick: () => alert('点击了按钮')
},
props.label || '默认按钮'
)
}
- 引入 并使用。
在任何页面中引入该常量组件即可直接使用。
总结关键操作点
在使用 h 函数替代模板时,需牢记以下操作细节:
- 扁平化数据结构:
props对象中包含class、style、id以及onXxx事件处理器。 - 子节点多样化:第三个参数
children可以是字符串(文本节点)、数组(多个子节点)或null(无子节点)。 - 插槽处理:在
setup的第二个参数或函数式组件的第二个参数中解构slots,通过slots.default()调用来获取 VNode。
直接使用 h 函数虽然牺牲了模板的直观性,但在处理高度动态的 UI、底层库开发或极致性能优化的场景中,它是不可替代的利器。

暂无评论,快来抢沙发吧!