容器/展示模式(X)

这是历史遗留问题,所以不建议使用(现在有了Hooks),在这里只是让我们了解这段历史

在 React 中,一种强制分离关注点的方法是通过使用容器/演示模式。使用这种模式,我们可以将视图与应用程序逻辑分开。


在 React 中,强制分离关注点的一种方法是使用 Container/Presentational 模式。 使用这种模式,我们可以将视图与应用程序逻辑分开。

假设我们要创建一个应用程序来获取 6 个狗的图像,并将这些图像呈现在屏幕上。

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

  1. 展示组件:关心如何向用户显示数据的组件。 在这个例子中,这是渲染狗图像列表。
  2. 容器组件:关心向用户显示什么数据的组件。 在这个例子中,这是获取狗的图像。

容器组件 获取狗图像处理应用程序逻辑,而 展示组件 只处理视图。


展示组件

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

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

1
2
3
4
5

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

容器组件

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

在示例中,我们希望将狗图像传递给 DogsImages 展示组件。
在能够这样做之前,需要从外部 API 获取图像。
需要创建一个容器组件来获取这些数据,并将这些数据传递给展示组件 DogImages 以便在屏幕上显示它。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

//容器组件 DogImagesContainer.js
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} />;
  }
}

将组件渲染到页面中

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

//将组件渲染到页面中
import React from "react";
import { render } from "react-dom";

import DogImagesContainer from "./DogImagesContainer";
import "./styles.css";

function App() {
  return (
    <div className="App">
      <h1>
        Browse Dog Images{" "}
        <span role="img" aria-label="emoji">
          🐕
        </span>
      </h1>
      <DogImagesContainer />
    </div>
  );
}

render(<App />, document.getElementById("root"));

Hooks

在很多情况下,Container/Presentational 模式可以用 React Hooks 代替。
Hooks 的引入使开发人员可以轻松添加状态,而无需容器组件来提供该状态。

我们可以创建一个自定义钩子来获取图像并返回狗的数组,而不是在 DogImagesContainer 组件中使用数据获取逻辑。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15

//自定义hook useDogImages.js
import { useState, useEffect } from "react";

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 组件中使用这个钩子!

1
2
3
4
5
6
7
8
9

//展示组件 DogImages.js
import useDogImages from "./useDogImages";

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

  return dogs.map((dog, i) => <img src={dog} key={i} alt="Dog" />);
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

//将展示组件渲染到页面
import React from "react";
import { render } from "react-dom";

import DogImages from "./DogImages";
import "./styles.css";

function App() {
  return (
    <div className="App">
      <h1>
        Browse Dog Images{" "}
        <span role="img" aria-label="emoji">
          🐕
        </span>
      </h1>
      <DogImages />
    </div>
  );
}

render(<App />, document.getElementById("root"));

通过使用 useDogImages 钩子,我们仍然将应用程序逻辑与视图分离。
我们只是使用从 useDogImages 钩子返回的数据,而没有在 DogImages 组件中修改该数据。

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


优点

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

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

展示组件很容易重用,因为它们只是显示数据而不改变这些数据。可以为不同的目的在整个应用程序中重用展示组件。

由于展示组件不会改变应用程序逻辑,因此不了解代码库的人(例如设计人员)可以轻松更改表示组件的外观。
如果在应用程序的许多部分重用了展示组件,则更改可以在整个应用程序中保持一致。

测试展示组件很容易,因为它们通常是纯函数。我们知道组件将根据我们传递的数据呈现什么,而无需模拟数据存储。


缺点

Container/Presentational 模式可以轻松地将应用程序逻辑与呈现逻辑分开。
但是,Hooks 可以实现相同的结果,而无需使用容器/展示模式,也无需将无状态功能组件重写为类组件。

尽管我们仍然可以使用容器/展示模式,甚至使用 React Hooks,但这种模式在较小规模的应用程序中很容易成为一种矫枉过正。


知识点

  • 纯函数展示组件
  • 自定义hooks