对我们的下一本书感兴趣?了解更多关于 使用 React 构建大型 JavaScript Web 应用

渲染模式

渲染函数

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() 函数本身接受三个参数

  1. 一个 HTML 标签名称或组件定义。
  2. 要传递给元素的道具/属性(事件监听器、类属性等)。
  3. 父节点的子节点。

我们想要构建的父节点的 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!”` 消息,这告诉我们已正确渲染了子组件。

Badge component

哇。如果你在这里感到困惑,不用担心。虽然渲染函数赋予了我们在定制组件标记方面更大的权力,但绝大多数情况下,使用标准模板通常要 *容易得多*。只有在需要复杂的动态渲染或定制的特殊情况下,才会选择渲染函数。

RenderComponent.vue
1<template>
2 <render />
3</template>
4
5<script setup>
6import { h } from "vue";
7
8/* eslint-disable-next-line no-undef, no-unused-vars */
9const { message } = defineProps(["message"]);
10
11/* 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 message
25 ),
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-vueVue CLI 都具有使用预配置 JSX 支持来搭建项目的选项。

RenderComponent.vue
1<template>
2 <render />
3</template>
4
5<script setup lang="jsx">
6const { message } = defineProps(["message"]);
7
8const 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!”。

RenderComponent.vue
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 }
8
9 export default RenderComponent;

总结

渲染函数提供了一种强大的方法,可以使用 JavaScript 以编程方式构建 Vue 组件的标记。它们使我们能够创建 Vue 用于跟踪并在页面上呈现的 DOM 节点的虚拟表示。

虽然渲染函数提供了灵活性和定制性,但与使用标准模板相比,它们可能更复杂。如果你感觉没有完全理解本文中的信息 - **这完全没有问题**。Vue 建议我们尽可能使用标准模板,因为渲染函数更难掌握和在应用程序中实现。但是,在需要在定制组件标记方面拥有更大权力和灵活性的特殊情况下,渲染函数很有用。

有用资源