渲染模式
渲染函数
Vue 建议我们使用模板(即 <template></template>
语法)来构建 Vue 组件的标记。但是,我们也有机会直接使用一种称为 **渲染函数** 的方法来构建组件的标记。
Vue 在构建时会获取我们为组件创建的模板,并将它们编译成渲染函数。在这些编译后的渲染函数中,Vue 会构建构成虚拟 DOM 的节点的虚拟表示。
如果你感兴趣,Vue 文档的 渲染机制 部分更详细地介绍了虚拟 DOM 和 Vue 内部渲染机制的概念。
通过使用渲染函数,我们跳过了 Vue 用于编译模板的编译步骤,并且能够借助编程 JavaScript 来构建组件模板。
但是为什么呢?
当我们需要更高程度的定制和灵活性,而这些定制和灵活性无法通过标准模板语法轻松实现时,渲染函数就会发挥作用。这乍一看可能有点违反直觉,尤其是考虑到 Vue 强调其模板系统的简洁性和可读性。简而言之,你可能更愿意使用渲染函数
- 当需要根据复杂的逻辑动态渲染组件或元素时,而这种逻辑在模板中表达起来可能很麻烦。
- 你想直接控制虚拟 DOM 以进行高级操作。
- 你想使用 JSX 来构建组件的模板。
除了这些特殊情况外,Vue 的模板语法应该仍然是构建组件标记的首选方法。但是,对于特殊情况,了解渲染函数的工作原理非常重要。因此,在本文中,我们将深入探讨渲染函数,并探讨如何使用它们来构建一个基本组件。
渲染函数
假设我们有以下组件,它包含一个包含 <header>
元素的 <div>
元素。<header>
元素的文本内容简单地显示 message
属性的值。
<template>
<div class="render-card">
<header class="card-header card-header-title">{{ message }}</header>
</div>
</template>
<script setup>
const { message } = defineProps(["message"]);
</script>
我们将借助渲染函数(即 h()
函数)逐步重新创建组件的标记 - 也就是 h()
函数。
<script setup>
import { h } from "vue";
const { message } = defineProps(["message"]);
</script>
h
是 **超文本** 的缩写,这是一个术语,通常在虚拟 DOM 实现中用来表示产生 HTML 的 JavaScript 语法。简单来说,h()
函数是渲染函数,它允许我们创建 Vue 用于跟踪并在页面上呈现的 DOM 节点的“虚拟”表示。
h()
函数本身接受三个参数
- 一个 HTML 标签名称或组件定义。
- 要传递给元素的道具/属性(事件监听器、类属性等)。
- 父节点的子节点。
我们想要构建的父节点的 HTML 标签名称是一个 <div>
元素。我们将 h()
函数的结果分配给一个名为 render
的常量,并将值为 'div'
的字符串作为第一个参数传递。
<script setup>
import { h } from "vue";
const { message } = defineProps(["message"]);
const render = () => {
return h("div");
};
</script>
我们感兴趣的是将 .render-card
CSS 类应用于父 <div>
元素。为此,我们将在 h()
函数的第二个参数中声明数据对象,使其具有一个值为 'render-card' 的 class
属性。
<script setup>
import { h } from "vue";
const { message } = defineProps(["message"]);
const render = () => {
return h("div", {
class: "render-card",
});
};
</script>
虽然我们在这个示例中不会做更多的事情,但使用第二个参数数据对象定义属性有很多不同的方法。如果你感兴趣,一定要查看 Vue 文档,以获得一个很好的总结。
我们希望父 <div>
元素拥有自己的子 <header>
元素。在 h()
函数的第三个参数中,我们可以指定一个简单的字符串来渲染文本,或者指定一个数组来渲染更多虚拟节点(即更多元素)。
由于我们将渲染另一个生成的元素作为子元素,因此我们将 h()
函数声明在子节点数组中,并将其赋值为 'header' 的字符串值。
<script setup>
import { h } from "vue";
const { message } = defineProps(["message"]);
const render = () => {
return h(
"div",
{
class: "render-card",
},
[h("header")]
);
};
</script>
头子元素应该有自己的类,因此我们将在一个嵌套的 h()
函数中传递一个属性对象,以声明头元素应该具有的类。
<script setup>
import { h } from "vue";
const { message } = defineProps(["message"]);
const render = () => {
return h(
"div",
{
class: "render-card",
},
[
h("header", {
class: "card-header card-header-title",
}),
]
);
};
</script>
子头元素不应包含任何子元素,而是简单地显示 message
属性的值。为了让头元素显示 message
属性作为其子内容,我们将在嵌套的 h()
函数的第三个参数中声明 message
的值。
<script setup>
import { h } from "vue";
const { message } = defineProps(["message"]);
const render = () => {
return h(
"div",
{
class: "render-card",
},
[
h(
"header",
{
class: "card-header card-header-title",
},
message
),
]
);
};
</script>
就是这样!我们剩下的唯一要做的事情就是将我们创建的 render
虚拟节点元素放在组件的模板部分。
<template>
<render />
</template>
<script setup>
import { h } from "vue";
/* eslint-disable-next-line no-undef, no-unused-vars */
const { message } = defineProps(["message"]);
/* eslint-disable-next-line no-unused-vars */
const render = () => {
return h(
"div",
{
class: "render-card",
},
[
h(
"header",
{
class: "card-header card-header-title",
},
message
),
]
);
};
</script>
我们现在可以继续在父 App.vue
实例中渲染上面的组件,并将 "Hello World!"
的值传递给 message
属性。
<template>
<RenderComponent message="Hello world!" />
</template>
<script setup>
import RenderComponent from "./components/RenderComponent.vue";
</script>
保存这些更改后,我们将看到 UI 中的 `“Hello World!”` 消息,这告诉我们已正确渲染了子组件。
哇。如果你在这里感到困惑,不用担心。虽然渲染函数赋予了我们在定制组件标记方面更大的权力,但绝大多数情况下,使用标准模板通常要 *容易得多*。只有在需要复杂的动态渲染或定制的特殊情况下,才会选择渲染函数。
1<template>2 <render />3</template>45<script setup>6import { h } from "vue";78/* eslint-disable-next-line no-undef, no-unused-vars */9const { message } = defineProps(["message"]);1011/* eslint-disable-next-line no-unused-vars */12const render = () => {13 return h(14 "div",15 {16 class: "render-card",17 },18 [19 h(20 "header",21 {22 class: "card-header card-header-title",23 },24 message25 ),26 ]27 );28};29</script>
渲染函数和 JSX
我们上面所做的实现之所以看起来有些痛苦,一个主要原因是我们用原始的原生 JavaScript 编写了渲染函数。为了帮助我们更容易地编写渲染函数,Vue 使我们能够借助适当的 Babel 插件 使用 JSX 来编写渲染函数!
如果你来自 React 背景,那么 JSX 可能已经是熟悉的主题了。简而言之,JavaScript XML(或者更常见地称为 JSX)是一种扩展,它允许我们编写看起来像 HTML 的 JavaScript(即在 JavaScript 中编写类似 XML 的语法)。
JSX 可以帮助我们重新创建渲染实现,使其更易于阅读,因为我们可以在渲染函数中安全地编写 HTML。
<template>
<render />
</template>
<script setup lang="jsx">
const { message } = defineProps(["message"]);
const render = (
<div class="render-card">
<header class="card-header card-header-title">{message}</header>
</div>
);
</script>
使用 JSX,我们的渲染函数看起来并不难!请记住,JSX 是一种开发工具,它始终需要借助 Babel 包(如 babel-plugin-jsx)进行转译为标准 JavaScript。 create-vue 和 Vue CLI 都具有使用预配置 JSX 支持来搭建项目的选项。
1<template>2 <render />3</template>45<script setup lang="jsx">6const { message } = defineProps(["message"]);78const render = <div class="render-card"><header class="card-header card-header-title">{message}</header></div>9</script>
函数式组件
函数式组件是一种渲染函数,它提供了一种使用 **纯函数** 定义组件的方法。函数式组件是一种独特的组件类型,它没有内部状态。它们类似于纯函数,接收道具作为输入,并生成虚拟节点作为输出。
要构建函数式组件,我们使用简单的函数而不是选项对象。此函数本质上充当渲染函数,负责生成组件的输出。
function RenderComponent(props, { slots, emit, attrs }) {
// ...
}
export default RenderComponent;
我们可以使用 h()
函数来创建组件的模板,正如我们之前所见。
import { h } from "vue";
function RenderComponent(props) {
return h(
"div",
{
class: "render-card",
},
[
h(
"header",
{
class: "card-header card-header-title",
},
props.message
),
]
);
}
export default RenderComponent;
此外,我们还可以使用 JSX 以更易于阅读的方式渲染组件的模板。
function RenderComponent(props) {
return (
<div class="render-card">
<header class="card-header card-header-title">{props.message}</header>
</div>
);
}
export default RenderComponent;
使用此函数式组件设置,我们的组件将在 UI 中渲染相同的“Hello World!”。
1function RenderComponent(props) {2 return (3 <div class="render-card">4 <header class="card-header card-header-title">{props.message}</header>5 </div>6 );7 }89 export default RenderComponent;
总结
渲染函数提供了一种强大的方法,可以使用 JavaScript 以编程方式构建 Vue 组件的标记。它们使我们能够创建 Vue 用于跟踪并在页面上呈现的 DOM 节点的虚拟表示。
虽然渲染函数提供了灵活性和定制性,但与使用标准模板相比,它们可能更复杂。如果你感觉没有完全理解本文中的信息 - **这完全没有问题**。Vue 建议我们尽可能使用标准模板,因为渲染函数更难掌握和在应用程序中实现。但是,在需要在定制组件标记方面拥有更大权力和灵活性的特殊情况下,渲染函数很有用。