Skip to content
在此页中

容器/展示模式

从应用逻辑中分离视图以强制分离关注点

在 React 中,一种实现 关注点分离 的方式是使用 容器/展示模式 。使用这种模式,我们能将视图与逻辑分开。


假设我们要创建一个应用来获取 6 张狗的图片,并将这些图片渲染在屏幕上。

理想情况下,我们希望通过将此过程分为两部分来实现 关注点分离

  1. 展示组件 :关注数据 如何 展示给用户的组件。在此例中,就是 渲染狗图片的列表
  2. 容器组件 :关注向用户显示 什么 数据的组件。在此例中,就是 获取狗的图片

获取狗的图片处理的是 应用逻辑 ,而显示图片只处理 视图

展示组件

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

让我们看一下显示狗的图片的例子。渲染狗的图片时,我们只想列举从 API 获取的每张狗的图片,然后渲染这些图片。为此,我们可以创建一个功能组件,它通过 props 接收数据,并渲染这些接收到的数据。

import React from "react";

export default function DogImages({ dogs }) {
  return dogs.map((dog, i) => <img src={dog} key={i} alt="Dog" />);
}

组件 DogImages 就是一个展示组件。展示组件 通常 是无状态的:它们不包含自己的 React 状态,除非它们出于 UI 目的需要状态。它们接收到的数据,不会被展示组件自身所修改。

展示组件从 容器组件 接收数据。

容器组件

容器组件的主要功能是 传递数据 给它所 包含 的展示组件。容器组件除了关注其数据的展示组件之外,并不会渲染任何其他组件。因为容器组件自身不渲染任何东西,它也通常不包含任何样式。

在我们的示例中,我们希望将狗的图片传递给 DogsImages 展示组件。在达成此点之前,需要从外部 API 获取图片。需要创建一个 容器组件 来获取此数据,并将这些数据传递给展示组件 DogsImages ,以将它显示在屏幕上。

import React from "react";
import DogImages from "./DogImages";

export default class DogImagesContainer extends React.Component {
  constructor() {
    super();
    this.state = {
      dogs: []
    };
  }

  componentDidMount() {
    fetch("https://dog.ceo/api/breed/labrador/images/random/6")
      .then(res => res.json())
      .then(({ message }) => this.setState({ dogs: message }));
  }

  render() {
    return <DogImages dogs={this.state.dogs} />;
  }
}

将这两种组件组合在一起使用就可以将应用逻辑与视图分开处理。

钩子

在许多情况下,容器/展示模式可以使用 React 钩子代替。钩子的引入使开发人员可以轻松添加有状态性,而无需容器组件来提供该状态。

可以创建一个自定义钩子来获取图片,并返回狗的数组,而不是在 DogImagesContainer 组件中去实现数据获取的逻辑。

JavaScript
export default function useDogImages() {
  const [dogs, setDogs] = useState([]);

  useEffect(() => {
    fetch("https://dog.ceo/api/breed/labrador/images/random/6")
      .then(res => res.json())
      .then(({ message }) => setDogs(message));
  }, []);

  return dogs;
}

通过使用这个钩子,就不再需要包装 DogImagesContainer 容器组件来获取数据,并将数据发送到展示组件 DogImages 。相反,可以直接在展示组件 DogImages 中使用这个钩子!

import React from "react";
import useDogImages from "./useDogImages";

export default function DogImages() {
  const dogs = useDogImages();

  return dogs.map((dog, i) => <img src={dog} key={i} alt="Dog" />);
}

通过使用 useDogImages 钩子,也能是应用逻辑与视图分开。这样做也只会使用从 useDogImages 钩子中获取到的数据,而不会修改 DogImages 组件中的数据。

钩子可以很容易地分离组件中的逻辑和视图,就像容器/展示模式一样。它节省了将展示组件包装在容器组件中所必须的额外层。

优点

使用容器/展示模式有很多好处。

容器/展示模式鼓励 关注点分离 。展示组件可以是负责 UI 的纯函数,而容器组件负责应用的状态和数据。这使得实现 关注点分离 变得容易。

展示组件很容易复用,因为它只是显示数据而不更改数据。可以出于不同的目的在整个应用中复用展示组件。

由于展示组件不会改变应用逻辑,其外观也很容易被不了解代码的人所更改,例如设计师。如果展示组件在应用的多处被复用,那么(对该组件的)更改就可以在整个应用中保持一致。

测试展示组件很容易,因为它们通常是纯函数。我们知道组件将根据我们传递的数据进行渲染,而无需模拟数据状态。

缺点

容器/展示模式很容易将应用逻辑与渲染逻辑分开。然而,钩子可以实现同样的效果,而无需使用此模式,也无需将无状态功能组件重构为类组件。请注意,现今,我们不再需要创建类组件来使用状态。

尽管我们仍然可以使用容器/展示模式,甚至可以跟 React 钩子一起使用,这种模式也容易在较小规模的应用中造成矫枉过正的局面。

参考

Creative Commons License