设计模式
容器/展示模式
在 React 中,一种强制分离关注点的方法是使用 容器/展示模式。使用这种模式,我们可以将视图与应用程序逻辑分离。
假设我们要创建一个应用程序,该应用程序获取 6 张狗狗图片,并在屏幕上渲染这些图片。
1import React from "react";23export default function DogImages({ dogs }) {4 return dogs.map((dog, i) => <img src={dog} key={i} alt="Dog" />);5}
理想情况下,我们希望通过将此过程分成两部分来强制分离关注点
- 展示组件:关心 如何 向用户显示数据的组件。在本例中,即 渲染狗狗图片列表。
- 容器组件:关心 什么 数据显示给用户。在本例中,即 获取狗狗图片。
获取狗狗图片处理 应用程序逻辑,而显示图片只处理 视图。
展示组件
展示组件通过 props
接收数据。它的主要功能只是简单地 按我们想要的方式显示它接收到的数据,包括样式,而不会修改这些数据。
让我们看看显示狗狗图片的例子。渲染狗狗图片时,我们只需要遍历从 API 获取的每个狗狗图片,并渲染这些图片。为此,我们可以创建一个接收数据通过 props
,并渲染它接收到的数据的函数组件。
1import React from "react";23export default function DogImages({ dogs }) {4 return dogs.map((dog, i) => <img src={dog} key={i} alt="Dog" />);5}
DogImages
组件是一个展示组件。展示组件通常是无状态的:它们不包含自己的 React 状态,除非它们需要用于 UI 目的的状态。它们接收到的数据不会被展示组件本身更改。
展示组件从 容器组件 接收数据。
容器组件
容器组件的主要功能是 传递数据 给展示组件,这些组件被它们 包含。容器组件本身通常不渲染任何其他组件,除了关心其数据的展示组件。由于它们本身不渲染任何东西,因此它们通常也不包含任何样式。
在本例中,我们要将狗狗图片传递给 DogsImages
展示组件。在能够做到这一点之前,我们需要从外部 API 获取这些图片。我们需要创建一个 容器组件,它获取这些数据,并将这些数据传递给展示组件 DogImages
,以便在屏幕上显示它。
1import React from "react";2import DogImages from "./DogImages";34export default class DogImagesContainer extends React.Component {5 constructor() {6 super();7 this.state = {8 dogs: []9 };10 }1112 componentDidMount() {13 fetch("https://dog.ceo/api/breed/labrador/images/random/6")14 .then(res => res.json())15 .then(({ message }) => this.setState({ dogs: message }));16 }1718 render() {19 return <DogImages dogs={this.state.dogs} />;20 }21}
将这两个组件组合在一起,就可以将应用程序逻辑处理与视图分离。
Hooks
在很多情况下,容器/展示模式可以被 React Hooks 取代。Hooks 的引入使开发人员能够轻松地添加状态,而无需容器组件来提供该状态。
而不是在 DogImagesContainer
组件中进行数据获取逻辑,我们可以创建一个自定义 Hook,它获取图片,并返回狗狗数组。
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;
}
通过使用这个 Hook,我们不再需要包装 DogImagesContainer
容器组件来获取数据,并将数据发送给展示的 DogImages
组件。相反,我们可以在展示的 DogImages
组件中直接使用这个 Hook!
1import React from "react";2import useDogImages from "./useDogImages";34export default function DogImages() {5 const dogs = useDogImages();67 return dogs.map((dog, i) => <img src={dog} key={i} alt="Dog" />);8}
通过使用 useDogImages
Hook,我们仍然将应用程序逻辑与视图分离。我们只是在 DogImages
组件中使用 useDogImages
Hook 返回的数据,而不会修改 DogImages
组件中的这些数据。
Hooks 使得在组件中分离逻辑和视图变得容易,就像容器/展示模式一样。它为我们节省了以前为了将展示组件包装在容器组件中而需要的额外层。
优点
使用容器/展示模式有很多好处。
容器/展示模式鼓励分离关注点。展示组件可以是纯函数,负责 UI,而容器组件负责应用程序的状态和数据。这使得强制分离关注点变得容易。
展示组件很容易被复用,因为它们只是简单地 显示 数据,而不会修改这些数据。我们可以在整个应用程序中出于不同的目的复用展示组件。
由于展示组件不会改变应用程序逻辑,因此展示组件的外观很容易被不了解代码库的人改变,例如设计师。如果展示组件在应用程序的许多部分被复用,那么这些更改可以在整个应用程序中保持一致。
测试展示组件很容易,因为它们通常是纯函数。我们知道这些组件将根据我们传递的数据进行渲染,而无需模拟数据存储。
缺点
容器/展示模式使将应用程序逻辑与渲染逻辑分离变得容易。但是,Hooks 使得在不使用容器/展示模式的情况下也能达到相同的目的,并且无需将无状态函数组件重写为类组件。请注意,今天我们不再需要创建类组件来使用状态了。
虽然我们仍然可以使用容器/展示模式,即使使用 React Hooks,但在较小规模的应用程序中,这种模式很容易过度使用。