presentational-container-pattern

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Container/Presentational Pattern

容器/展示组件模式

In React, one way to enforce separation of concerns is by using the Container/Presentational pattern. With this pattern, we can separate the view from the application logic.
在React中,实现关注点分离的一种方式是使用容器/展示组件模式。通过这种模式,我们可以将视图与应用逻辑分离。

When to Use

适用场景

  • Use this when you want a clear separation between data-fetching logic and UI rendering
  • This is helpful for making presentational components reusable and easy to test
  • 当你希望清晰区分数据获取逻辑与UI渲染逻辑时使用
  • 有助于让展示组件具备可复用性,且更易于测试

Instructions

实施说明

  • Container components handle data fetching and state; presentational components handle rendering
  • Prefer custom Hooks over container components in modern React for the same separation of concerns
  • Keep presentational components as pure functions that receive data through props
  • Use this pattern when it genuinely simplifies your architecture — avoid it for small components
  • 容器组件负责数据获取与状态管理;展示组件负责渲染UI
  • 在现代React中,优先使用自定义Hooks而非容器组件来实现相同的关注点分离
  • 保持展示组件为纯函数,通过props接收数据
  • 仅当该模式能真正简化架构时才使用——小型组件无需采用

Details

详细说明

Let's say we want to create an application that fetches 6 dog images, and renders these images on the screen.
Ideally, we want to enforce separation of concerns by separating this process into two parts:
  1. Presentational Components: Components that care about how data is shown to the user. In this example, that's the rendering the list of dog images.
  2. Container Components: Components that care about what data is shown to the user. In this example, that's fetching the dog images.
Fetching the dog images deals with application logic, whereas displaying the images only deals with the view.
假设我们要创建一个应用,获取6张狗狗图片并在页面上展示。
理想情况下,我们可以通过将流程拆分为两部分来实现关注点分离:
  1. 展示组件:关注如何向用户展示数据的组件。在这个例子中,就是_渲染狗狗图片列表_的部分。
  2. 容器组件:关注哪些数据要展示给用户的组件。在这个例子中,就是_获取狗狗图片_的部分。
获取狗狗图片属于应用逻辑,而展示图片仅涉及视图层

Presentational Component

展示组件

A presentational component receives its data through
props
. Its primary function is to simply display the data it receives the way we want them to, including styles, without modifying that data.
Let's take a look at the example that displays the dog images. When rendering the dog images, we simply want to map over each dog image that was fetched from the API, and render those images. In order to do so, we can create a functional component that receives the data through
props
, and renders the data it received.
The
DogImages
component is a presentational component. Presentational components are usually stateless: they do not contain their own React state, unless they need a state for UI purposes. The data they receive, is not altered by the presentational components themselves.
Presentational components receive their data from container components.
展示组件通过
props
接收数据。它的核心功能是按照我们想要的方式展示接收到的数据,包括样式,但不会修改这些数据。
我们来看展示狗狗图片的示例。渲染图片时,我们只需遍历从API获取的每张狗狗图片并渲染出来。为此,我们可以创建一个函数组件,通过
props
接收数据并进行渲染。
DogImages
组件就是一个展示组件。展示组件通常是无状态的:它们自身不包含React状态,除非是用于UI相关的状态。它们接收到的数据不会被自身修改。
展示组件从容器组件获取数据。

Container Components

容器组件

The primary function of container components is to pass data to presentational components, which they contain. Container components themselves usually don't render any other components besides the presentational components that care about their data. Since they don't render anything themselves, they usually do not contain any styling either.
In our example, we want to pass dog images to the
DogsImages
presentational component. Before being able to do so, we need to fetch the images from an external API. We need to create a container component that fetches this data, and passes this data to the presentational component
DogImages
in order to display it on the screen.
Combining these two components together makes it possible to separate handling application logic with the view.
容器组件的核心功能是向其包含的展示组件传递数据。容器组件自身通常除了传递数据的展示组件外,不会渲染其他组件。由于它们自身不渲染内容,通常也不包含样式。
在我们的示例中,我们需要将狗狗图片传递给
DogImages
展示组件。在此之前,我们需要从外部API获取图片。我们需要创建一个容器组件来获取数据,并将数据传递给
DogImages
展示组件以在页面上展示。
将这两个组件结合使用,就能实现应用逻辑与视图层的分离。

Hooks

Hooks

In many cases, the Container/Presentational pattern can be replaced with React Hooks. The introduction of Hooks made it easy for developers to add statefulness without needing a container component to provide that state.
Instead of having the data fetching logic in the
DogImagesContainer
component, we can create a custom hook that fetches the images, and returns the array of dogs.
js
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;
}
By using this hook, we no longer need the wrapping
DogImagesContainer
container component to fetch the data, and send this to the presentational
DogImages
component. Instead, we can use this hook directly in our presentational
DogImages
component!
By using the
useDogImages
hook, we still separated the application logic from the view. We're simply using the returned data from the
useDogImages
hook, without modifying that data within the
DogImages
component.
Hooks make it easy to separate logic and view in a component, just like the Container/Presentational pattern. It saves us the extra layer that was necessary in order to wrap the presentational component within the container component.
在很多场景下,容器/展示组件模式可以被React Hooks替代。Hooks的出现让开发者无需通过容器组件提供状态,就能轻松为组件添加状态管理能力。
我们无需在
DogImagesContainer
组件中编写数据获取逻辑,而是可以创建一个自定义Hook来获取图片并返回狗狗图片数组。
js
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!
使用
useDogImages
Hook,我们依然实现了应用逻辑与视图层的分离。我们只是使用
useDogImages
Hook返回的数据,不会在
DogImages
组件中修改这些数据。
Hooks让组件内的逻辑与视图分离变得简单,效果和容器/展示组件模式一样。它还省去了为包裹展示组件而必须添加的额外层级。

Pros

优点

There are many benefits to using the Container/Presentational pattern.
The Container/Presentational pattern encourages the separation of concerns. Presentational components can be pure functions which are responsible for the UI, whereas container components are responsible for the state and data of the application. This makes it easy to enforce the separation of concerns.
Presentational components are easily made reusable, as they simply display data without altering this data. We can reuse the presentational components throughout our application for different purposes.
Since presentational components don't alter the application logic, the appearance of presentational components can easily be altered by someone without knowledge of the codebase, for example a designer. If the presentational component was reused in many parts of the application, the change can be consistent throughout the app.
Testing presentational components is easy, as they are usually pure functions. We know what the components will render based on which data we pass, without having to mock a data store.
使用容器/展示组件模式有诸多好处。
该模式有助于实现关注点分离:展示组件作为纯函数负责UI部分,而容器组件负责应用的状态与数据管理。这让关注点分离的实现变得简单。
展示组件具备良好的可复用性,因为它们只是展示数据而不修改数据。我们可以在应用的不同场景中复用这些展示组件。
由于展示组件不涉及应用逻辑,即使是不熟悉代码库的人员(例如设计师)也能轻松修改展示组件的外观。如果该展示组件在应用的多个部分被复用,修改后的效果也能在整个应用中保持一致。
展示组件的测试也很简单,因为它们通常是纯函数。我们可以根据传入的数据预知组件的渲染结果,无需模拟数据存储。

Cons

缺点

The Container/Presentational pattern makes it easy to separate application logic from rendering logic. However, Hooks make it possible to achieve the same result without having to use the Container/Presentational pattern, and without having to rewrite a stateless functional component into a class component. Note that today, we don't need to create class components to use state anymore.
Although we can still use the Container/Presentational pattern, even with React Hooks, this pattern can easily be an overkill in smaller sized application.
Note (React 18+): Modern React strongly favors Hooks over container components for separating logic from views. Custom Hooks can replace class-based containers entirely — for example, a
useDogImages
hook can fetch data using
useState
and
useEffect
, then any component can simply call
const dogs = useDogImages()
to get the data. This achieves the same separation of concerns (data fetching vs UI) with less boilerplate and no wrapper component. This Hook-based approach is also friendly to React's upcoming optimizations — the React Compiler can better optimize functional components and Hooks than class lifecycles.
容器/展示组件模式虽然能轻松实现应用逻辑与渲染逻辑的分离,但Hooks让我们无需使用该模式、也无需将无状态函数组件改写为类组件就能达到同样的效果。需要注意的是,如今我们已经不需要通过类组件来使用状态了。
尽管结合React Hooks我们依然可以使用容器/展示组件模式,但对于小型应用来说,该模式很可能是过度设计。
注意(React 18+): 现代React强烈推荐使用Hooks而非容器组件来分离逻辑与视图。自定义Hooks可以完全替代基于类的容器组件——例如,
useDogImages
Hook可以通过
useState
useEffect
获取数据,然后任何组件只需调用
const dogs = useDogImages()
就能获取数据。这种方式实现了相同的关注点分离(数据获取与UI),且代码更简洁,无需额外的包裹组件。这种基于Hook的方式也更适配React未来的优化——React编译器对函数组件和Hooks的优化效果优于类组件的生命周期。

Source

来源

References

参考资料