
引言
在 Vue 开发中,模板语法 是我们最常用的视图编写方式 ------ 简洁直观、指令丰富,能满足 80% 的日常开发需求。但面对一些高度动态的场景时,模板语法的局限性就会暴露出来:
- 动态生成不同标签或组件时,需要写大量
v-if/v-else嵌套,代码冗余且可读性差; - 复杂的条件渲染逻辑混入模板中,导致视图与逻辑耦合严重;
- 无法直接通过 JavaScript 灵活控制组件的创建与销毁。
为了解决这些痛点,Vue 提供了渲染函数(Render Function) 和 JSX 两种进阶方案。渲染函数是 Vue 底层的视图生成机制,而 JSX 是渲染函数的语法糖,让我们可以用更接近 HTML 的写法编写动态视图。
本文将从原理剖析 、语法精讲 、实战对比三个维度,带你彻底搞懂渲染函数与 JSX 的用法,结合 流程图和可直接复用的代码示例,帮你突破模板语法的瓶颈,写出更灵活、更高效的 Vue 代码。
1. 前置认知:模板语法的痛点与渲染函数的价值
1.1 模板语法的典型痛点
我们先看一个需求:根据后端返回的 tag 类型,动态生成不同的标签或组件 。比如 tag 为 a 时渲染链接,为 button 时渲染按钮,为 custom 时渲染自定义组件。
用模板语法实现的代码如下:
html
<template>
<div>
<a v-if="tag === 'a'" :href="href" @click="handleClick">{{ text }}</a>
<button v-else-if="tag === 'button'" @click="handleClick">{{ text }}</button>
<CustomComponent v-else-if="tag === 'custom'" @click="handleClick">{{ text }}</CustomComponent>
</div>
</template>
可以看到,当 tag 类型增多时,v-if/v-else 会无限嵌套,代码变得臃肿且难以维护。
1.2 渲染函数的核心价值
渲染函数的本质是用 JavaScript 代替模板语法,直接生成虚拟 DOM(VNode)。它的核心优势在于:
- 高度灵活:可以通过 JavaScript 的条件判断、循环、函数调用等逻辑,动态生成任意结构的视图;
- 逻辑与视图解耦:复杂的渲染逻辑可以封装在函数中,视图结构由逻辑直接决定;
- 性能更优:跳过模板编译步骤,直接生成 VNode,减少运行时开销。
Vue 模板的底层也是通过编译成渲染函数来生成 VNode 的,我们直接使用渲染函数,相当于绕开了编译层,直接与 Vue 内核对话。
2. 渲染函数核心:h 函数深度解析
2.1 什么是 h 函数?
h 函数(Hyperscript 的缩写,意为 "生成超文本的函数")是 Vue 提供的创建虚拟 DOM 节点(VNode) 的核心函数。所有的模板语法最终都会被编译成 h 函数的调用。
其工作流程可以用下图直观表示:

2.2 h 函数的语法与参数
h 函数的基本语法非常简洁,接收三个参数,返回一个 VNode 对象:
javascript
h(tag, props, children)
参数详解
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
tag |
String / Object / Function | 是 | 要创建的节点类型:1. 字符串:HTML 原生标签(如 div、button)2. 对象:Vue 组件选项(如 CustomComponent)3. 函数:异步组件 |
props |
Object | 否 | 节点的属性、事件、指令等配置对象 |
children |
String / Array / VNode | 否 | 节点的子节点:1. 字符串:文本内容2. 数组:子节点列表(可以是字符串或 VNode)3. VNode:通过 h 函数创建的子节点 |
基础用法示例
我们用 h 函数实现 1.1 节的动态标签需求,代码如下:
html
<template>
<div>
<!-- 渲染函数的挂载点 -->
<render-dynamic-tag :tag="tag" :href="href" :text="text" @click="handleClick"/>
</div>
</template>
<script>
import { h } from 'vue'
import CustomComponent from './CustomComponent.vue'
export default {
components: {
// 注册渲染函数组件
RenderDynamicTag: {
props: ['tag', 'href', 'text'],
// 核心:render 函数返回 h 函数创建的 VNode
render() {
// 根据 tag 动态生成不同节点
switch (this.tag) {
case 'a':
return h('a', { href: this.href, onClick: this.$emit('click') }, this.text)
case 'button':
return h('button', { onClick: this.$emit('click') }, this.text)
case 'custom':
return h(CustomComponent, { onClick: this.$emit('click') }, this.text)
default:
return h('div', {}, this.text)
}
}
}
},
data() {
return {
tag: 'button',
href: 'https://www.baidu.com',
text: '动态按钮'
}
},
methods: {
handleClick() {
console.log('点击了动态节点')
}
}
}
</script>
可以看到,通过 switch 逻辑直接控制节点类型,代码比模板语法简洁得多,扩展性也更强。
2.3 h 函数的进阶用法
(1)创建带子节点的复杂结构
h 函数的 children 参数支持数组,可用于创建嵌套结构:
javascript
// 生成 <div><h1>标题</h1><p>内容</p></div>
render() {
return h('div', {}, [
h('h1', {}, '标题'),
h('p', {}, '内容')
])
}
(2)绑定事件与属性
在 props 参数中绑定原生事件、组件事件和 HTML 属性:
javascript
render() {
return h('button', {
// HTML 属性
id: 'btn',
class: ['btn-primary', 'btn-large'],
// 原生事件
onClick: () => console.log('点击了按钮'),
// 组件自定义事件
onCustomEvent: this.handleCustomEvent
}, '点击我')
}
(3)渲染异步组件
tag 参数支持异步组件,实现按需加载:
javascript
// 定义异步组件
const AsyncComponent = () => import('./AsyncComponent.vue')
render() {
return h(AsyncComponent, {}, '加载中...')
}
3. JSX 在 Vue 中的应用:从配置到实战
3.1 什么是 JSX?
JSX 是 JavaScript XML 的缩写,是一种在 JavaScript 中书写 HTML 语法的扩展。它的本质是语法糖 ------Vue 会将 JSX 编译成 h 函数的调用,最终生成 VNode。
JSX 的优势在于:
- 比
h函数更直观,HTML 结构一目了然; - 支持 JavaScript 表达式嵌入,兼顾灵活性和可读性;
- 对 React 开发者友好,学习成本低。
3.2 Vue 中使用 JSX 的配置
(1)Vue3 + Vite 配置
Vue3 对 JSX 有原生支持,只需安装 @vitejs/plugin-vue-jsx 插件:
- 安装依赖
bash
npm install @vitejs/plugin-vue-jsx -D
- 配置
vite.config.js
javascript
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueJsx from '@vitejs/plugin-vue-jsx'
export default defineConfig({
plugins: [vue(), vueJsx()]
})
(2)Vue2 + Webpack 配置
Vue2 需要安装 @vue/babel-preset-jsx 插件,具体配置可参考 Vue 官方文档,这里不再赘述。
3.3 JSX 核心语法(Vue 与 React 差异)
JSX 的语法和 HTML 类似,但有一些关键差异,尤其是和 React JSX 的区别,需要重点注意:
| 语法点 | Vue JSX 写法 | React JSX 写法 | 说明 |
|---|---|---|---|
| 插值表达式 | { this.text } |
{ this.text } |
一致,用大括号嵌入 JS 表达式 |
| 事件绑定 | onClick |
onClick |
一致,使用驼峰命名 |
| 类名绑定 | class={ ['btn', { active: true }] } |
className="btn active" |
Vue 支持数组 / 对象语法,React 用 className |
| 样式绑定 | style={ { color: 'red', fontSize: '14px' } } |
同 Vue | 一致,使用对象语法 |
| 指令 | 无,需手动实现 | 无 | Vue 模板指令(如 v-model)在 JSX 中需手动处理 |
| 组件标签 | 首字母大写(如 <CustomComponent>) |
同 Vue | 一致,区分原生标签和组件 |
3.4 JSX 实战案例:动态表单组件
我们用 JSX 实现一个动态表单组件,支持根据配置生成不同类型的表单项(输入框、下拉框、复选框):
html
<template>
<div class="dynamic-form">
<dynamic-form-item v-for="item in formItems" :key="item.key" :config="item" v-model="formData[item.key]"/>
</div>
</template>
<script setup>
import { ref } from 'vue'
import DynamicFormItem from './DynamicFormItem.jsx'
// 表单配置项
const formItems = ref([
{ key: 'name', type: 'input', label: '姓名', placeholder: '请输入姓名' },
{ key: 'gender', type: 'select', label: '性别', options: ['男', '女'] },
{ key: 'agree', type: 'checkbox', label: '同意协议' }
])
// 表单数据
const formData = ref({
name: '',
gender: '男',
agree: false
})
</script>
创建 DynamicFormItem.jsx 文件:
javascript
import { defineComponent } from 'vue'
export default defineComponent({
props: {
config: {
type: Object,
required: true
},
modelValue: {
type: [String, Number, Boolean],
default: ''
}
},
emits: ['update:modelValue'],
render() {
const { config, modelValue } = this
const { type, label, placeholder, options } = config
// 处理 v-model:手动实现双向绑定
const onChange = (e) => {
let value = e.target ? e.target.value : e
this.$emit('update:modelValue', value)
}
// 根据 type 生成不同表单项
let formItem = null
switch (type) {
case 'input':
formItem = <input value={modelValue} placeholder={placeholder} onChange={onChange}/>
break
case 'select':
formItem = (
<select value={modelValue} onChange={onChange}>
{options.map(opt => <option value={opt}>{opt}</option>)}
</select>
)
break
case 'checkbox':
formItem = <input type="checkbox" checked={modelValue} onChange={(e) => onChange(e.target.checked)}/>
break
}
// 返回 JSX 结构
return (
<div class="form-item">
<label class="form-label">{label}</label>
<div class="form-control">{formItem}</div>
</div>
)
}
})
这个案例中,JSX 既保留了 HTML 的结构清晰性,又通过 JavaScript 逻辑实现了动态渲染,比模板语法更灵活。
4. 实战对比:模板 vs h 函数 vs JSX
为了更直观地展示三者的差异,我们用同一个需求 分别实现:生成一个带标题和列表的组件。
需求描述
- 标题文本由父组件传入;
- 列表数据由父组件传入,渲染成
<li>列表; - 点击列表项触发事件。
4.1 模板语法实现
html
<template>
<div class="list-component">
<h2>{{ title }}</h2>
<ul>
<li v-for="item in list" :key="item.id" @click="handleClick(item)">{{ item.name }}</li>
</ul>
</div>
</template>
<script>
export default {
props: ['title', 'list'],
methods: {
handleClick(item) {
this.$emit('item-click', item)
}
}
}
</script>
特点:简洁直观,适合简单场景,学习成本低。
4.2 h 函数实现
javascript
import { h } from 'vue'
export default {
props: ['title', 'list'],
render() {
return h('div', { class: 'list-component' }, [
h('h2', {}, this.title),
h('ul', {}, this.list.map(item =>
h('li', {
key: item.id,
onClick: () => this.$emit('item-click', item)
}, item.name)
))
])
}
}
特点:高度灵活,适合动态结构,但可读性较差,嵌套较深时容易混乱。
4.3 JSX 实现
javascript
import { defineComponent } from 'vue'
export default defineComponent({
props: ['title', 'list'],
emits: ['item-click'],
render() {
return (
<div class="list-component">
<h2>{this.title}</h2>
<ul>
{this.list.map(item => (
<li key={item.id} onClick={() => this.$emit('item-click', item)}>
{item.name}
</li>
))}
</ul>
</div>
)
}
})
特点:兼顾结构清晰性和逻辑灵活性,是 h 函数的 "升级版",适合复杂动态场景。
4.4 三者对比总结
| 维度 | 模板语法 | h 函数 | JSX |
|---|---|---|---|
| 可读性 | 高 | 低 | 中高 |
| 灵活性 | 低 | 高 | 中高 |
| 学习成本 | 低 | 中 | 中(React 开发者友好) |
| 适用场景 | 简单静态 / 半静态视图 | 高度动态的视图结构 | 复杂动态视图,兼顾可读性 |
5. 避坑指南:渲染函数与 JSX 的 5 个常见误区
5.1 误区 1:JSX 中使用 v-model 指令
问题 :在 JSX 中直接写 v-model 无效,因为 JSX 不支持 Vue 模板指令。解决方案 :手动实现双向绑定,监听 input 事件并更新 modelValue:
javascript
// 正确写法
<input value={this.modelValue} onChange={(e) => this.$emit('update:modelValue', e.target.value)}/>
5.2 误区 2:h 函数事件名使用 @ 符号
问题 :在 h 函数的 props 中写 @click 无效,Vue 不识别这种写法。解决方案 :事件名使用驼峰命名法,如 onClick、onChange:
javascript
// 错误写法
h('button', { '@click': this.handleClick }, '按钮')
// 正确写法
h('button', { onClick: this.handleClick }, '按钮')
5.3 误区 3:忘记给列表项加 key
问题 :用 h 函数或 JSX 渲染列表时,忘记加 key 属性,导致 Vue 无法正确复用节点,引发性能问题或渲染错误。解决方案 :必须给列表项添加唯一的 key:
html
// JSX 正确写法
<ul>
{list.map(item => <li key={item.id}>{item.name}</li>)}
</ul>
5.4 误区 4:JSX 中类名使用 class 属性
问题 :在 Vue2 的 JSX 中,使用 class 属性可能无效(部分版本兼容问题)。解决方案 :Vue2 中可使用 class 或 className,Vue3 推荐使用 class,支持数组 / 对象语法:
html
// Vue3 JSX 类名绑定
<div class={ ['container', { active: true }] }></div>
5.5 误区 5:渲染函数中使用 this.$refs
问题 :在 render 函数中直接使用 this.$refs 可能获取不到 DOM,因为 render 函数执行时 DOM 尚未生成。解决方案 :在 mounted 钩子中获取 $refs:
javascript
export default {
render() {
return h('div', { ref: 'container' }, '内容')
},
mounted() {
console.log(this.$refs.container) // 正确获取 DOM
}
}
6. 选型建议:什么时候用模板 / 渲染函数 / JSX?
技术选型的核心是 贴合业务场景,没有绝对的优劣之分,只有最适合的选择:
6.1 优先使用模板语法的场景
- 大部分简单静态或半静态的视图(如普通页面、表单、列表);
- 团队成员以 Vue 新手为主,追求低学习成本和高可读性;
- 需要使用 Vue 模板特有的指令(如
v-for、v-if、v-show)的场景。
6.2 优先使用 h 函数的场景
- 高度动态的视图结构,如根据后端配置生成不同的组件;
- 需要动态创建异步组件,实现按需加载的场景;
- 开发 Vue 底层组件库,需要极致的灵活性。
6.3 优先使用 JSX 的场景
- 复杂动态视图,希望兼顾结构清晰性和逻辑灵活性;
- 团队中有 React 开发者,熟悉 JSX 语法;
- 需要编写大量条件渲染和循环渲染的组件(如动态表单、可视化组件)。
7. 总结与展望
渲染函数与 JSX 是 Vue 进阶开发的必备技能,它们弥补了模板语法的局限性,让我们可以用更灵活的方式控制视图渲染。
- h 函数是 Vue 视图生成的底层核心,是模板和 JSX 的 "幕后黑手",适合追求极致灵活性的场景;
- JSX 是 h 函数的语法糖,兼顾了 HTML 的直观性和 JavaScript 的灵活性,是复杂动态场景的首选;
- 模板语法依然是 Vue 的主流方案,适合大部分日常开发场景。
未来,随着 Vue3 和 Vite 的普及,JSX 的使用体验会越来越好,尤其是和 组合式 API(Composition API) 结合时,能写出更优雅、更易维护的代码。同时,Vue 官方也在持续优化 JSX 的编译性能,让它在大型项目中更具竞争力。
最后,记住一个核心原则:选择最适合当前业务场景和团队技术栈的方案,才是最好的方案。
点赞 + 收藏 + 关注,更多 Vue 高级特性干货持续更新中!有任何渲染函数或 JSX 的使用问题,欢迎在评论区留言讨论~
写在最后
本文力求做到原理讲透、实战落地、避坑全面,所有代码示例均可直接复现。如果你觉得这篇文章对你有帮助,欢迎转发给更多需要的朋友!
