Vue 高级特性:渲染函数与 JSX 精讲(h 函数语法、JSX 在 Vue 中的应用)

引言

在 Vue 开发中,模板语法 是我们最常用的视图编写方式 ------ 简洁直观、指令丰富,能满足 80% 的日常开发需求。但面对一些高度动态的场景时,模板语法的局限性就会暴露出来:

  • 动态生成不同标签或组件时,需要写大量 v-if/v-else 嵌套,代码冗余且可读性差;
  • 复杂的条件渲染逻辑混入模板中,导致视图与逻辑耦合严重;
  • 无法直接通过 JavaScript 灵活控制组件的创建与销毁。

为了解决这些痛点,Vue 提供了渲染函数(Render Function)JSX 两种进阶方案。渲染函数是 Vue 底层的视图生成机制,而 JSX 是渲染函数的语法糖,让我们可以用更接近 HTML 的写法编写动态视图。

本文将从原理剖析语法精讲实战对比三个维度,带你彻底搞懂渲染函数与 JSX 的用法,结合 流程图和可直接复用的代码示例,帮你突破模板语法的瓶颈,写出更灵活、更高效的 Vue 代码。

1. 前置认知:模板语法的痛点与渲染函数的价值

1.1 模板语法的典型痛点

我们先看一个需求:根据后端返回的 tag 类型,动态生成不同的标签或组件 。比如 taga 时渲染链接,为 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 原生标签(如 divbutton)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 插件:

  1. 安装依赖
bash 复制代码
npm install @vitejs/plugin-vue-jsx -D
  1. 配置 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 不识别这种写法。解决方案 :事件名使用驼峰命名法,如 onClickonChange

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 中可使用 classclassName,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-forv-ifv-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 的使用问题,欢迎在评论区留言讨论~

写在最后

本文力求做到原理讲透、实战落地、避坑全面,所有代码示例均可直接复现。如果你觉得这篇文章对你有帮助,欢迎转发给更多需要的朋友!

相关推荐
GGGG寄了2 小时前
CSS——文字控制属性
前端·javascript·css·html
菜鸟茜2 小时前
ES6核心知识解析01:什么是ES6以及为什么需要ES6
前端·javascript·es6
David凉宸2 小时前
Vue 3 项目的性能优化策略:从原理到实践(页面展示)
javascript·vue.js·性能优化
C澒2 小时前
FE BLL 架构:前端复杂业务的逻辑治理方案
前端·架构·前端框架·状态模式
摘星编程2 小时前
在OpenHarmony上用React Native:ImageGIF动图播放
javascript·react native·react.js
摘星编程2 小时前
React Native + OpenHarmony:Text文本高亮显示
javascript·react native·react.js
止观止2 小时前
拒绝“都是 string”:品牌类型与领域驱动设计 (DDD)
前端·typescript
芸简新章2 小时前
微前端:从原理到实践,解锁复杂前端架构的模块化密码
前端·架构
忧郁的Mr.Li2 小时前
设计模式--单例模式
javascript·单例模式·设计模式