渲染函数与 JSX
本教程共 40 篇 · 第 36 篇 · 更新于 2026-07-29 · 约 8 分钟阅读
36. 渲染函数与 JSX
本节目标:学会在模板不够用时,用渲染函数和 JSX 直接操作虚拟 DOM。
为什么需要渲染函数
模板能覆盖大部分场景。但有些逻辑太动态——比如根据配置生成不同结构的组件——模板写起来很别扭,渲染函数更自然。
渲染函数是模板编译后的产物。直接写渲染函数,相当于跳过了模板层,有 JavaScript 的全部灵活性。
h() 函数
Vue 提供了 h() 来创建虚拟节点(vnode):
import { h } from 'vue'
const vnode = h(
'div', // 标签名或组件
{ id: 'foo', class: 'bar' }, // props
[ // 子节点
h('span', 'hello')
]
)
h() 是 hyperscript 的缩写——“生成 HTML 的 JS”。
它的参数很灵活:
// 只有类型
h('div')
// 类型 + props
h('div', { id: 'foo' })
// 类型 + 子节点(props 为 null)
h('div', 'hello')
h('div', [h('span', 'hello')])
// 完整形式
h('div', { class: 'bar' }, [h('span', 'hello')])
class 和 style 支持对象和数组:
h('div', {
class: ['foo', { bar: true }],
style: { color: 'red' }
})
事件监听用 onXxx 形式:
h('div', { onClick: () => {} })
声明渲染函数
组合式 API 里,在 setup() 里返回渲染函数:
import { ref, h } from 'vue'
export default {
props: { msg: String },
setup(props) {
const count = ref(1)
return () => h('div', props.msg + count.value)
}
}
Options API 里用 render 选项:
export default {
data() {
return { msg: 'hello' }
},
render() {
return h('div', this.msg)
}
}
Tip
setup()只调用一次,渲染函数会被调用多次。所以返回函数而不是直接返回 vnode。
vnode 必须唯一
同一棵树里不能有重复的 vnode 引用:
// 错误!p 引用重复了
function render() {
const p = h('p', 'hi')
return h('div', [p, p])
}
想重复元素,用工厂函数:
function render() {
return h('div',
Array.from({ length: 20 }).map(() => h('p', 'hi'))
)
}
模板语法对照表
v-if
<!-- 模板 -->
<div>
<div v-if="ok">yes</div>
<span v-else>no</span>
</div>
// 渲染函数
h('div', [ok.value ? h('div', 'yes') : h('span', 'no')])
v-for
<!-- 模板 -->
<ul>
<li v-for="{ id, text } in items" :key="id">{{ text }}</li>
</ul>
// 渲染函数
h('ul', items.value.map(({ id, text }) => {
return h('li', { key: id }, text)
}))
v-on
h('button', { onClick: handleClick }, 'Click Me')
事件修饰符:
// 捕获模式和 once 直接拼在事件名后
h('input', {
onClickCapture() {},
onKeyupOnce() {}
})
// 其他修饰符用 withModifiers
import { withModifiers } from 'vue'
h('div', {
onClick: withModifiers(() => {}, ['self'])
})
组件
import Foo from './Foo.vue'
// 渲染函数里不需要注册组件,直接用
function render() {
return h('div', [h(Foo)])
}
动态组件:
function render() {
return ok.value ? h(Foo) : h(Bar)
}
插槽
export default {
setup(props, { slots }) {
return () => [
h('div', slots.default()),
h('div', slots.footer({ text: props.message }))
]
}
}
自定义指令
import { h, withDirectives } from 'vue'
const pin = { mounted() {}, updated() {} }
const vnode = withDirectives(h('div'), [
[pin, 200, 'top', { animate: true }]
])
JSX
JSX 是 JavaScript 的语法扩展,可以像写 HTML 一样写 UI:
const vnode = <div>hello</div>
动态值用花括号:
const vnode = <div id={dynamicId}>hello, {userName}</div>
模板与 JSX 等价对照
// v-if
<div>{ok.value ? <div>yes</div> : <span>no</span>}</div>
// v-for
<ul>
{items.value.map(({ id, text }) => (
<li key={id}>{text}</li>
))}
</ul>
// v-on
<button onClick={handleClick}>Click Me</button>
NoteVue 的 JSX 和 React 的 JSX 不一样。在 Vue JSX 里你可以用
class而不是className,用for而不是htmlFor。插槽语法也不同。
函数式组件
没有自身状态的组件可以写成纯函数——输入 props,输出 vnode:
function Hello(props, { slots, emit, attrs }) {
return h('div', props.message)
}
不需要组件实例,不触发生命周期钩子。适合纯展示型组件,性能更好。
也可以直接写成返回值:
function Hello() {
return 'hello world!'
}
内置组件的渲染函数写法
<Transition>、<KeepAlive>、<Teleport> 这些内置组件在渲染函数里必须显式 import:
import { h, KeepAlive, Transition } from 'vue'
export default {
setup() {
return () => h(Transition, { mode: 'out-in' }, () => 'content')
}
}
模板引用
Vue 3.5+ 用 useTemplateRef:
import { h, useTemplateRef } from 'vue'
export default {
setup() {
const divEl = useTemplateRef('my-div')
return () => h('div', { ref: 'my-div' })
}
}
本节回顾
h()创建 vnode,是模板编译后的底层 API- 渲染函数在
setup()或render()里返回 - vnode 必须唯一,重复用工厂函数
- JSX 提供更接近 HTML 的写法
- 函数式组件没有状态,性能更好
- 模板语法都有对应的渲染函数写法
- 优先用模板,高度动态的场景才用渲染函数