容器/展示模式
在 React 中,一种实现 关注点分离 的方式是使用 容器/展示模式 。使用这种模式,我们能将视图与逻辑分开。
假设我们要创建一个应用来获取 6 张狗的图片,并将这些图片渲染在屏幕上。
理想情况下,我们希望通过将此过程分为两部分来实现 关注点分离 :
- 展示组件 :关注数据 如何 展示给用户的组件。在此例中,就是 渲染狗图片的列表 ;
- 容器组件 :关注向用户显示 什么 数据的组件。在此例中,就是 获取狗的图片 。
获取狗的图片处理的是 应用逻辑 ,而显示图片只处理 视图 。
展示组件
展示组件通过 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
组件中去实现数据获取的逻辑。
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 钩子一起使用,这种模式也容易在较小规模的应用中造成矫枉过正的局面。