Skip to content

VueJS - 渲染函数

虽然 Vue 的模板功能强大,足以覆盖绝大多数用例,但有时您需要 JavaScript 的完全编程能力来创建动态组件。这时就需要用到渲染函数。Vue 3 提供了 h 函数(“hyperscript” 的缩写),用于在渲染函数或 setup 函数中创建虚拟 DOM 节点。

渲染函数通常用于高级场景,或者在编写需要比模板提供更高灵活性的高度可重用组件时使用。

  • 对组件结构具有完全的编程控制能力。
  • 创建需要根据 Props 动态决定 HTML 标签的组件。
  • 在非常特定、罕见的情况下进行性能优化(尽管 Vue 的模板编译器已经高度优化)。
  • 与期望 JavaScript 函数进行渲染的库集成。

对于大多数应用而言,使用单文件组件(SFCs)中的模板是推荐且更易维护的方法。

h 函数是渲染函数的核心。它通常从 ‘vue’ 导入。

import { h } from 'vue'
// h(type, props, children)
// type: String (HTML tag), Object (Component), Function (Async Component)
// props: Object with attributes, props, event listeners etc.
// children: String (text) or Array of VNodes (other h() calls) or Slot function

让我们使用 Vue 3 的 h 函数重现一个类似原始示例的组件。这个组件将渲染一个带动态文本内容和样式的标题(h1 到 h6)。

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Vue 3 Render Function Example</title>
<script src="https://unpkg.com/vue@3"></script>
</head>
<body>
<div id="app">
<dynamic-heading :level="1" style="color: blue;">Main Title</dynamic-heading>
<dynamic-heading :level="3" class="sub-title">Subtitle</dynamic-heading>
<dynamic-heading :level="currentLevel" :style="dynamicStyle">Dynamic Level Heading</dynamic-heading>
<button @click="increaseLevel">Increase Level (Current: {{ currentLevel }})</button>
</div>
<script>
const { createApp, h, ref, computed } = Vue;
const app = createApp({
setup() {
const currentLevel = ref(4);
const dynamicStyle = computed(() => ({
color: currentLevel.value > 3 ? 'red' : 'green',
borderBottom: '1px solid black'
}));
function increaseLevel() {
if (currentLevel.value < 6) {
currentLevel.value++;
} else {
currentLevel.value = 1; // Cycle back
}
}
return { currentLevel, dynamicStyle, increaseLevel };
}
});
// 使用渲染函数的动态标题组件
app.component('dynamic-heading', {
props: {
level: { // 标题级别 (1-6)
type: Number,
required: true,
validator: (value) => value >= 1 && value <= 6
}
// 其他属性如 'style', 'class' 通过渲染函数上下文的 $attrs 自动传递
},
setup(props, { slots, attrs }) { // 访问 props, slots, attrs
// 在 setup 中定义渲染函数
return () => { // 返回渲染函数本身
const tag = `h${props.level}`; // 计算标签名
// 使用 h() 创建 VNode
return h(
tag, // 元素类型
attrs, // 传递非 props 属性 (如 style, class)
slots.default ? slots.default() : [] // 渲染默认插槽内容
);
};
}
// --- Options API 等价写法 ---
/*
props: { level: { type: Number, required: true } },
render() {
const tag = `h${this.level}`;
return h(
tag,
this.$attrs, // 传递属性
this.$slots.default ? this.$slots.default() : [] // 访问插槽
);
}
*/
});
app.mount('#app');
</script>
<style>
.sub-title { font-style: italic; color: gray; }
</style>
</body>
</html>
  • 我们从 Vue 导入 h(或在 CDN 示例中通过全局的 Vue.h 访问)。
  • dynamic-heading 组件接受一个 level prop (1-6)。
  • 在 Composition API 的 setup 函数中,我们返回另一个函数。这个返回的函数才是真正的渲染函数。
  • 在渲染函数内部,我们根据 props.level 动态确定标签名(例如 'h1'、'h2')。
  • 我们调用 h(tag, attrs, slots.default()):
  • tag:动态确定的 HTML 标签字符串。
  • attrs:这个对象(由 setup 的第二个参数 context 提供,通过解构获得 { attrs })包含传递给组件但未声明为 props 的所有属性(例如本例中的 style 和 class)。传递这些属性可以确保应用在组件标签上的样式和类能按预期工作。
  • slots.default():这访问组件标签之间传递的默认插槽内容(例如 ‘Main Title’)。我们将其作为函数调用以获取插槽内容的 VNodes 数组。我们添加了一个检查 (slots.default ? ... : []) 以防没有提供插槽内容。
  • Options API 的等价写法直接使用 render() 方法,通过 this.level 访问 props,通过 this.$attrs 访问属性,通过 this.$slots 访问插槽。

Vue 也支持在渲染函数中使用 JSX (JavaScript XML),这感觉更像编写模板。这需要构建工具配置(例如为 Vite 添加 @vitejs/plugin-vue-jsx)。

// 使用 JSX 的示例 (需要构建设置)
import { defineComponent } from 'vue';
export default defineComponent({
props: {
level: { type: Number, required: true }
},
setup(props, { slots, attrs }) {
const Tag = `h${props.level}`; // 注意: JSX 标签需要用大写变量
return () => (
<Tag {...attrs}>
{ slots.default ? slots.default() : '' }
</Tag>
);
}
});

JSX 允许直接在 JavaScript 中编写类似 HTML 的语法,它会被编译成 h 函数调用。注意,动态标签名需要存储在一个大写的变量 (Tag) 中,以便 JSX 将其识别为动态组件/标签。

当模板功能不足时,渲染函数和 JSX 提供了最大的灵活性。然而,它们比模板更冗长,不够声明式。在复杂的动态渲染场景或构建高度可配置的底层组件时,请谨慎使用它们。

更多详情,请参考官方文档:Render Functions & JSX。