首页 / Vue3 入门教程 / 渲染函数与 JSX

Vue3 入门教程

渲染函数与 JSX

本教程共 40 篇 · 第 36 篇 · 更新于 2026-07-29 · 约 8 分钟阅读

VueVue3 入门教程Render FunctionJSXh()函数式组件

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>
Note

Vue 的 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 的写法
  • 函数式组件没有状态,性能更好
  • 模板语法都有对应的渲染函数写法
  • 优先用模板,高度动态的场景才用渲染函数