Skip to content

渲染函数

Vue 建议我们使用模板(即 <template></template> 语法)来构建 Vue 组件的 HTML 部分。然而,我们同样可以直接使用渲染函数来构建组件的 HTML 部分。

Vue 会在构建的时候将我们在组件中编写的模板编译为渲染函数。也正是在这些渲染函数中,Vue 构建了全部节点的一个虚拟体现,也就是虚拟 DOM。

如果你感兴趣,Vue 文档中的 渲染机制 一节中更为详细地介绍了虚拟 DOM 的概念和 Vue 内部的渲染机制。

通过使用渲染函数,我们跳过了 Vue 对我们的模板进行编译的编译步骤,并且可以使用编程形式的 JavaScript 来构建我们的模板。

为什么要用渲染函数

当我们需要更多的灵活性和更高级别的定制化需求时,渲染函数就会开始发挥作用了。而标准的模板无法轻松做到这一点。这可能听上去有些违反直觉,特别是考虑到 Vue 一直在强调其模板的简单和易读。但是,简而言之,你可能在某些情况下更偏爱渲染函数:

  • 当你需要基于复杂的逻辑,动态渲染组件时,在模板中表达这些逻辑将会特别麻烦。

  • 你想要直接使用虚拟 DOM,进行一些高级的操作。

  • 你想要在构建模板或组件时使用 JSX。

除了上述的特殊情况外,Vue 的模板应该依然是构建组件 HTML 的首选方法。然而,对于特殊情况下,了解渲染函数的运行方式是很重要的一件事。因此,我们将在本文中深入研究渲染函数,并探索如何使用渲染函数来构建基本的组件。

渲染函数

假设我们有一个组件,组件的模板部分包含一个 <div> 元素,这个 div 还包裹着一个 <header> 元素。<header> 元素显示了一行文本,文本内容来自 props 中 message 的值。

vue
<template>
  <div class="render-card">
    <header class="card-header card-header-title">{{ message }}</header>
  </div>
</template>

<script setup>
  const { message } = defineProps(["message"]);
</script>

接下来我们将使用渲染函数(即 h() 函数),逐步重写这个组件的 HTML 部分。

vue
<script setup>
  import { h } from "vue";

  const { message } = defineProps(["message"]);
</script>

hhyperscript 的缩写,它是实现虚拟 DOM 的过程中经常使用的术语,意思是 “可以生成 HTML 的 JavaScript”。简而言之, h() 是渲染函数,它允许我们将一个 DOM 节点“虚拟”地表示出来,Vue 会跟踪这个节点的虚拟表示,并随后渲染在页面上。

h() 函数有三个参数:

  1. 一个 HTML 标签名,或者是组件定义。

  2. 传入元素的 props/attributes(事件监听、class 属性等)。

  3. 该节点下的子节点。

我们要构造的父节点 HTML 标签名是 <div>。我们将 h() 函数的返回值分配给一个常量 render,并传入一个字符串 'div' 作为第一个参数。

vue
<script setup>
  import { h } from "vue";

  const { message } = defineProps(["message"]);

  const render = () => {
    return h("div");
  };
</script>

接下来我们想要给父元素 <div> 添加一个 .render-card CSS 类。我们需要为 h() 函数的第二个参数传入一个对象,其中包含一个 class 属性,属性值是 render-card

vue
<script setup>
  import { h } from "vue";

  const { message } = defineProps(["message"]);

  const render = () => {
    return h("div", {
      class: "render-card",
    });
  };
</script>

在这个例子中,对于第二个参数的使用仅此而已。我们不会介绍关于第二个参数的更多事情了。但是还有很多种用法来使用第二个参数来定义元素的属性。如果你有兴趣了解,可以查看 Vue 文档

我们希望父元素 <div> 拥有一个子元素 <header>。在 h() 函数的第三格参数,我们可以传递一个字符串来渲染文本,或者是传递一个数组来渲染更多的虚拟节点(即更多子元素)。

由于我们需要将另一个渲染的元素 <header> 作为 <div> 的子元素,因此我们要将 h() 函数放在第三个参数的子节点数组中,并传入参数 header 作为第一个参数:

vue
<script setup>
  import { h } from "vue";

  const { message } = defineProps(["message"]);

  const render = () => {
    return h(
      "div",
      {
        class: "render-card",
      },
      [h("header")]
    );
  };
</script>

子元素 header 也需要 class,所以我们向内部的 h() 函数传入 header 需要的 class:

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

子元素 header 不包含子元素,只需要将 prop message 显示出来。为了让 header 显示 message,我们将 message 传入内部的 h() 函数的第三个参数中。

vue
<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 放在组件的模板上。

vue
<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 实例中,然后为 message prop 传递一个值 "Hello World!"

vue
<template>
  <RenderComponent message="Hello world!" />
</template>

<script setup>
  import RenderComponent from "./components/RenderComponent.vue";
</script>

保存代码后,我们将看到 "Hello World!" 已经显示在了界面上。我们已经成功渲染了组件。

alt text

如果你感到困惑,无需担心。尽管渲染函数为我们提供了更多的功能来定制组件的 HTML,但在绝大多数情况下,使用标准的模板语法会来得更容易些。只有在一些复杂的动态渲染或是特殊情况下,人们才会选择渲染函数。

在演练场中查看代码

渲染函数和 JSX

我们为了实现上面的例子,过程可能看上去有些痛苦。出现这个感觉的很大部分原因是因为,我们使用了原生的 JavaScript 来编写渲染函数。为了能更简单地编写渲染函数,Vue 为我们提供了可以使用 JSX 编写渲染函数的能力。要使用 JSX 编写渲染函数,需要借助 Babal 插件

如果你有开发 React 项目的背景,JSX 对你来说可能已经很熟悉了。简而言之,JavaScript XML (也就是常说的 JSX)是一种允许我们使用看起来像 HTML 的 JavaScript 编写渲染函数的扩展。通过 JSX,我们可以在 JavaScript 中编写类似 XML 的代码。

JSX 可以以一种具有更高可读性的方式重新实现我们的渲染逻辑,因为我们可以在渲染函数中直接编写 HTML:

vue
<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 支持。

在演练场中查看代码

函数式组件

函数式组件,是一种 通过纯函数 方式定义组件的渲染函数。函数式组件是一种没有内部状态的特别的组件。它们很像是单纯的函数,接收 props 作为输入,然后通过返回值输出虚拟节点。

我们使用一个简单的函数(而不是包含选项的对象)来构建一个函数式组件。这个函数本质上是一个渲染函数,主要负责生成组件并输出。

javascript
function RenderComponent(props, { slots, emit, attrs }) {
  // ...
}

export default RenderComponent;

我们使用 h() 函数创建我们组件的模板,就像之前看到的那样。

javascript
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 渲染组件的模板,以获得更高的可读性。

javascript
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!"

在演练场中查看代码

总结

渲染函数提供了强大的能力让我们可以使用 JavaScript 以编程的方式来构建 Vue 组件的 HTML。渲染函数允许我们创建 Vue 中 DOM 节点的虚拟表示,这有利于 Vue 对其状态进行跟踪和渲染。

虽然渲染函数具有更高的灵活性和可定制性,但与标准模板相比,渲染函数仍然更复杂一些。如果你觉得仍然没懂本文的内容,那完全没有关系。Vue 建议我们尽可能使用标准的模板语法,因为渲染函数更难掌握,也不容易编写。然而,渲染函数在一些特定的场景下十分有用,例如在自定义组件的 HTML 时需要更多的功能以及更高灵活性的时候。

扩展阅读

评论区
评论区空空如也
发送评论
名字
0 / 20
邮箱
0 / 100
评论内容
0 / 140
由于是非实名评论,所以不提供删除功能。如果你需要删除你发送的评论,或者是其他人的评论对你造成了困扰,请 发邮件给我 。同时评论区会使用 AI + 人工的方式进行审核,以达到合规要求。

© thebestxt.cc
辽ICP备16009524号-8
本站所有文章版权所有,转载请注明出处