渲染函数
官网地址:
https://cn.vuejs.org/guide/extras/render-function
推荐先看一下vue官方的渲染机制:
https://cn.vuejs.org/guide/extras/rendering-mechanism.html
一、渲染函数的核心概念与应用场景
1.1 为什么需要渲染函数?
- 模板的局限性:虽然模板语法直观易用,但在处理高度动态的渲染逻辑(如根据数据结构动态生成组件树)或封装可复用的高级组件(如表单生成器、可视化编辑器)时,模板的灵活性会受限。
- 性能与底层控制:渲染函数直接操作虚拟 DOM(VNode),避免模板编译开销,适合高频更新场景,同时能实现模板无法完成的底层 DOM 控制。
1.2 渲染函数与 Vue 版本的关系
- Vue 2:h函数是Vue.extend和模板编译的底层实现,通过render选项使用。
- Vue 3:h函数是 Composition API 中render函数的基础,配合setup函数使用更灵活,且优化了 VNode 结构和 diff 算法。
二、深入理解 h 函数:创建虚拟节点的核心工具
2.1 创建Vnodes
vue提供了一个h()
函数创建vnodes
import {h} from 'vue'
const vnode = h(
type, // 必选:节点类型(HTML标签/组件/函数)
[propsOrChildren], // 可选:属性对象或子节点
[children] // 可选:子节点(当第二个参数为属性时)
)
- type
- HTML 标签:h(‘div’)、h(‘button’)
- 组件对象:
import MyComponent from './MyComponent.vue'; h(MyComponent)
- 函数:返回 VNode 的函数,如h(() => h(‘span’, ‘动态节点’))
- propsOrChildren
- 属性对象:包含 DOM 特性、组件 Prop、事件等,如
{ id: 'box', class: 'container' }
- 子节点(字符串 / 数组):当省略属性时,第二个参数为子节点,如
h('div', '文本内容')
- 属性对象:包含 DOM 特性、组件 Prop、事件等,如
- children
- 支持嵌套 VNode 数组,如
h('div', null, [h('p', '段落1'), h('p', '段落2')])
- 支持嵌套 VNode 数组,如
2. 2 h函数的使用
除了类型必填以外,其他的参数都是可选的
h('div') h('div', { id: 'foo' })
attribute 和 property 都能在 prop 中书写
https://www.doubao.com/thread/w0b21c1cca8c7e48ch('div', { class: 'bar', innerHTML: 'hello' })
像
.prop
和.attr
这样的的属性修饰符,可以分别通过.
和^
前缀来添加h('div', { '.name': 'some-name', '^width': '100' })
类与样式可以像在模板中一样,用数组或对象的形式书写
h('div', { class: [foo, { bar }], style: { color: 'red' } })
事件监听器应以 onXxx 的形式书写
h('div', { onClick: () => {} })
children 可以是一个字符串
h('div', { id: 'foo' }, 'hello')
没有 props 时可以省略不写
h('div', 'hello') h('div', [h('span', 'hello')])
children 数组可以同时包含 vnodes 与字符串
h('div', ['hello', h('span', 'hello')])
类与样式的灵活写法
const isActive = true
const size = 'large'
h('div', {
// 类名支持数组/对象组合
class: [
'container',
{ 'active': isActive },
{ [`size-${size}`]: size }
],
// 样式支持对象/字符串
style: {
color: 'red',
fontSize: '16px',
...(isActive ? { fontWeight: 'bold' } : {})
}
})
Fragment 支持(Vue 3 特有)
Fragment 是一种虚拟 DOM 概念,简单说就是允许组件返回多个根节点(虚拟节点,VNode ),而无需用一个额外标签包裹 。
// 返回多个节点(自动包裹在Fragment中)
h('div', [
h('h1', '标题'),
h('p', '内容1'),
h('p', '内容2')
])
三、props 深入:渲染函数中的数据传递机制
props 是用于描述虚拟节点对应的 DOM 元素或组件的属性、事件等信息的对象
3.1 props 的本质与作用
- 组件通信桥梁:在渲染函数中,props是父组件向子组件传递数据的对象,等价于模板中的v-bind属性绑定。
- 类型安全:通过声明props的类型、默认值和必填项,实现数据校验,提升组件健壮性。
3.2 在渲染函数中使用 props
- 3.2.1 传递 props 给组件
import ChildComponent from './ChildComponent.vue'
// 父组件中用h函数传递props
h(ChildComponent, {
title: '子组件标题',
count: 10,
user: { name: '张三', age: 25 },
onUpdate: (val) => console.log('更新值:', val)
})
- 3.2.2 子组件声明 props(Vue 3 示例)
<template>
<div>
<h3>{{ title }}</h3>
<p>用户:{{ user.name }} ({{ user.age }})</p>
</div>
</template>
<script setup>
import { defineProps } from 'vue'
// 声明props(对象形式)
const props = defineProps({
title: {
type: String,
default: '默认标题'
},
count: {
type: Number,
required: true
},
user: {
type: Object,
default: () => ({ name: '未知', age: 0 })
}
})
</script>
3.3 属性修饰符:精准控制 prop 与 attribute
- .prop修饰符(明确作为组件 prop)
// 子组件声明props: { name: String }
h(ChildComponent, {
'.name': '李四' // 等价于模板中的:name="李四"
})
- ^attr修饰符(明确作为 DOM attribute)
// 渲染为<div data-id="123"></div>
h('div', {
'^data-id': '123' // 强制作为HTML attribute
})
// 给组件的根元素添加attribute(即使组件未声明该prop)
h(ChildComponent, {
'^data-role': 'admin' // 会出现在组件根元素的DOM上
})
四、实战案例:渲染函数与 props 的组合应用
4.1 动态生成表单组件
import { h, ref } from 'vue'
// 表单字段配置
const fields = [
{ type: 'text', label: '姓名', model: 'name' },
{ type: 'number', label: '年龄', model: 'age' },
{ type: 'checkbox', label: '同意协议', model: 'agreed' }
]
// 表单数据
const formData = ref({
name: '张三',
age: 25,
agreed: false
})
// 渲染函数
export default {
setup() {
return () => h('form', { class: 'dynamic-form' }, [
// 循环生成表单项
fields.map(field => h('div', { class: 'form-group' }, [
h('label', null, field.label),
h('input', {
type: field.type,
id: field.model,
value: formData.value[field.model],
onChange: (e) => {
formData.value[field.model] =
field.type === 'checkbox' ? e.target.checked : e.target.value
}
})
]),
// 提交按钮
h('button', {
type: 'submit',
class: 'btn-primary',
onClick: (e) => {
e.preventDefault()
console.log('表单提交:', formData.value)
}
}, '提交')
])
}
}
4.2 高阶组件(HOC)封装
import { h } from 'vue'
// 权限验证HOC
const withAuth = (Component) => {
return {
setup() {
const isAuthenticated = ref(false)
// 模拟权限验证
setTimeout(() => {
isAuthenticated.value = true // 假设验证通过
}, 1000)
return () => isAuthenticated.value
? h(Component) // 有权限时渲染组件
: h('div', { class: 'auth-error' }, '无访问权限')
}
}
}
// 使用HOC
const ProtectedComponent = withAuth({
setup() {
return () => h('div', '受保护的内容')
}
})
// 渲染HOC组件
h(ProtectedComponent)
五、性能优化与最佳实践
5.1 合理使用 key 提升 diff 效率
// 列表渲染时指定key
h('ul', null,
list.map(item => h('li', { key: item.id }, item.text))
)
5.2 避免过度使用渲染函数
- 优先模板:模板语法足以满足需求时,避免强行使用渲染函数,保持代码可读性。
- 组件封装:将复杂渲染逻辑封装为组件,通过 props 和事件暴露接口,而非在单个组件中堆砌渲染函数。
5.3 结合 JSX 提升开发效率
// 需要配置Babel/TS支持JSX
import { h } from 'vue'
const app = () => (
<div class="app">
<h1>JSX标题</h1>
<button onClick={() => alert('点击')}>操作按钮</button>
<ChildComponent :count={10} />
</div>
)
六、Vue 2 与 Vue 3 中渲染函数的差异
七、总结:渲染函数的适用场景与学习建议
7.1 推荐使用场景
动态组件生成:根据 API 数据动态决定组件结构(如表单生成器、可视化编辑器)。
高阶组件封装:实现权限控制、加载状态等通用逻辑的复用。
性能敏感场景:高频更新的列表或图表组件,避免模板编译开销。
一般不使用h函数,大多数情况下,当我们使用的第三方库不满足我们需要的api时,但是有需要对其样式进行修改,可以使用h函数动态修改