# 关注点分离

2015 年,Dan Abramov 写了一篇题为“Presentational and Container Components”的文章,改变了许多开发人员对 React 中组件架构的看法。他引入了一种模式,将组件分为两类:

  1. 外观组件。这些与事物的外观有关。它们没有指定数据是如何加载或更改的,而是专门通过 props 接收数据和回调。主要关心如何向用户显示数据的组件。
  2. 容器组件。这些组件与事物的运作方式有关。它们为表示或其他容器组件提供数据和行为。主要关心向用户显示哪些数据的组件。

虽然这种模式主要与 React 相关,但它的基本原理在其他库和框架中以各种形式被采用和改编。

Dan 的区别为构建 JavaScript 应用程序提供了一种更清晰、更具可扩展性的方法。通过明确定义不同类型组件的职责,开发人员可以确保 UI 组件(表示)和逻辑(容器)具有更好的可重用性。我们的想法是,如果我们要改变某样东西的外观(比如按钮的设计),我们可以在不触及 app 的逻辑的情况下进行。相反,如果我们需要更改数据的流动或处理方式,则表示组件将保持不变,从而确保 UI 保持一致。

# 表示组件(纯渲染组件)

表示组件通过 props 接收其数据。它的主要功能是简单地以我们希望的方式显示它接收到的数据,包括样式,而不修改这些数据。

让我们看一下显示狗图像的示例。在渲染狗图像时,我们只想映射从 API 获取的每个狗图像并渲染这些图像。为此,我们可以创建一个 DogImages 组件,该组件通过 props 接收数据并呈现它接收到的数据。

<!-- DogImages.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 目的的状态。他们接收到的数据不会被表示组件本身改变。

# 容器组件(纯逻辑组件)

容器组件的主要功能是将数据传递给它们包含的表示组件。容器组件本身通常不会渲染除了关心其数据的表示组件之外的任何其他组件。由于它们本身不渲染任何内容,因此它们通常也不包含任何样式。

在我们的示例中,我们希望将狗图像传递给 DogsImages 表示组件。在能够执行此操作之前,我们需要从外部 API 获取图像。我们需要创建一个容器组件来获取这些数据,并将这些数据传递给表示组件 DogImages 以在屏幕上显示它。我们将此容器组件称为 DogImagesContainer。

<!-- 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 等状态管理解决方案集成时,可以利用容器组件直接与 store 交互,根据需要获取或更改状态。这使得表示组件可以保持纯粹,并且不必知道应用程序逻辑,只专注于根据它们接收到的 props 渲染 UI。