Skip to content

容器/展示模式

2015 年,Dan Abramov 写了一篇标题为 展示和容器组件(Presentational and Container Components )的文章,改变了很多开发者在 React 中组件架构的思考方式。他引入了一种新的模式,这种模式将组件分为了两类:

  1. 展示组件(或哑组件、无状态组件): 这类组件只关注元素如何展示。它们不关心数据如何加载如何变化,只通过 props 接收数据和回调方法。

  2. 容器组件(或智能组件): 它们关心一切如何运作。它们向展示组件或其它的容器组件提供数据和具体的行为。

虽然这种模式主要用在 React 上,但这其中的基本原理很快被其它库或框架以各种形式采用。

Dan 提供的这种组件分类方式提供了一种更清晰并可扩展的方式来构建 JavaScript 应用。通过明确定义不同类型的组件的职责,开发者可以确保展示组件和容器组件各司其职,具有更好的可复用性。如果我们需要改变某些组件的外观(例如按钮的外观),我们可以在不触及应用程序逻辑的情况下达成修改的目的。相反地,如果我们需要改变数据流向或是具体的处理逻辑,我们也可以保持展示组件中的逻辑不变,从而确保 UI 保持一致。

然而,随着 React 中的 hooks 和 Vue 3 中 组合式 API 的出现,展示组件和容器组件的界限变得越来越模糊。 Hooks 和组合式 API 开始允许开发者封装和复用状态和逻辑,而不必局限于基于类的组件或者是选项式 API。因此容器/展示模式不需要再像以前那样严格遵守了。话虽如此,我们仍然需要在本文中花一些时间来讨论一下这个模式,因为它在某些时候仍然很有用。


我们接下来要创建一个应用程序,获取 6 张狗狗图片,并将图片渲染到屏幕上。

alt text

为了遵循容器/展示模式,我们希望通过将这个过程分为两部分,以此达成关注点分离:

  1. 展示组件:它关注数据是如何展示给用户的。在这个例子中,就是狗图片的渲染。

  2. 容器组件:它关注展示给用户什么数据。在这个例子中,就是狗图片的获取。

使用 应用程序逻辑 获取狗狗图片,然而显示图片仅仅是用 视图 来处理。

展示组件

展示组件通过 props 获取到数据。它的主要功能就是按照我们期望的方式,简单地 显示它得到的数据,包括使用什么样的样式来显示,但不需要对数据进行修改。

我们看一下这个现实狗狗图片的例子。当渲染狗狗图片时,我们仅需要将从接口中获取到的狗狗图片映射在组件上,并渲染它们。为此,我们可以创建一个 DogImages 组件,它通过 props 接收数据,并为用户呈现接收到的数据。

vue
<template>
  <img v-for="(dog, index) in dogs" :src="dog" :key="index" alt="Dog" />
</template>

<script setup>
  import { defineProps } from "vue";
  const { dogs } = defineProps(["dogs"]);
</script>

DogImages 组件被视为一个展示组件。展示组件通常是无状态的,它们不具备自己的组件状态,除非它们的 UI 需要一个状态来保存与 UI 相关的数据。它们接收到的数据并不会被它们修改。

展示组件从 容器组件 中收到它们的数据。

容器组件

容器组件的主要作用是向展示组件 传递 它们所需的 数据。容器组件不会渲染任何组件,除了那些需要当前容器组件中的数据的展示组件。因为容器组件不渲染任何视图,所以它们通常也不会包含任何的样式。

在我们的例子中,我们想要向 DogImages 这个展示组件传递狗狗图片。在开始之前,我们需要从一个外部接口中获取图片数据。我们需要先创建一个 容器组件 来获取数据,然后将数据传递到展示组件 DogImages 中,让展示组件将它们显示在屏幕上。我们要创建的这个容器组件叫作 DogImagesContainer

vue
<template>
  <DogImages :dogs="dogs" />
</template>

<script setup>
  import { ref, onMounted } from "vue";
  import DogImages from "./DogImages.vue";

  const dogs = ref([]);

  onMounted(async () => {
    const response = await fetch(
      "https://dog.ceo/api/breed/labrador/images/random/6"
    );
    const { message } = await response.json();
    dogs.value = message;
  });
</script>

比较这两个组件,可以发现我们将程序的逻辑和视图分开处理了。

简而言之,这就是所谓的容器/展示模式。当应用中集成了类似 Pinia 等状态管理工具时,这时可以让容器组件直接和状态进行交互,根据具体需求获取或更改状态。这也让展示组件的功能更加纯粹,它不需要知晓更多的应用逻辑,只需要专注于基于它们所得到的属性来渲染界面即可。

在演练场中查看代码

组合式

确保你已经阅读过了组合式,这样接下来才能对其深入了解。

大多情况下,容器/展示模式都可以被组合式替代。组合式的出现,让开发者开发展示组件变得十分简单,当使用组合式的时候, 不需要 容器组件为其组件提供状态。

不需要在 DogImagesContainer 中编写获取数据的逻辑,取而代之的是,我们可以创建一个组合式组件来获取数据,然后返回狗狗照片的数组。

javascript
import { ref, onMounted } from "vue";

export default function useDogImages() {
  const dogs = ref([]);

  onMounted(async () => {
    const response = await fetch(
      "https://dog.ceo/api/breed/labrador/images/random/6"
    );
    const { message } = await response.json();
    dogs.value = message;
  });

  return { dogs };
}

我们通常会将这种组合式函数称作「钩子 hook」。通过使用这个 hook,我们再也不需要使用 DogImagesContainer 这个容器组件来获取数据,再将数据发送给展示组件 DogImages 了。取而代之的是,我们可以直接在展示组件 DogImages 中使用这个 hook!

vue
<template>
  <img v-for="(dog, index) in dogs" :src="dog" :key="index" alt="Dog" />
</template>

<script setup>
  import useDogImages from "../composables/useDogImages";

  /* eslint-disable-next-line no-unused-vars */
  const { dogs } = useDogImages();
</script>

通过使用 useDogImages() hook,我们仍然需要从视图中分离应用逻辑。我们只需要使用 useDogImages hook 的返回值,不需要在 DogImages 组件中修改数据。

通过一番修改,我们的应用看上去像之前那样一样了。

在演练场中查看代码

组合式让逻辑和视图分离这件事变得十分简单,就像容器/展示模式一样。组合式为我们节省了额外的一层程序逻辑。

扩展阅读

Vue 组合式 | Patterns.dev

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

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