关于 container-presentational
container-presentational 是面向 Vue 开发者的组件架构模式指南,将数据获取与行为交给容器组件,将渲染交给展示组件。内容也说明 Vue 3 中可用 composable 实现类似分离,避免额外包装层。
主要功能
- 01区分容器组件与展示组件
- 02通过 props 传递数据和回调
- 03让展示组件专注渲染
- 04介绍 composable 替代方案
适用场景
- 01分离数据获取与界面渲染
- 02复用并测试展示组件
- 03在 Vue 3 中用 composable 组织逻辑
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ patternsdev/ skills --skill container-presentational安装后在 Claude Code 中输入/container-presentational确认已加载。
详细介绍
容器/展示组件模式
目录
2015 年,Dan Abramov 发表了一篇名为《展示组件与容器组件》的文章,改变了许多开发者对 React 组件架构的看法。他提出了一种将组件分为两类的模式:
- 展示组件(或哑组件):关注事物的外观。它们不负责指定数据的加载或修改方式,而是完全通过 props 接收数据和回调。
- 容器组件(或智能组件):关注事物的工作方式。它们向展示组件或其他容器组件提供数据和行为。
适用场景
- 当你希望清楚地分离数据获取逻辑与 UI 渲染时,使用此模式
- 这有助于让展示组件更易于复用和测试
说明
- 容器组件负责处理数据获取和状态;展示组件通过 props 处理渲染
- 在 Vue 3 中,优先使用组合式函数来实现相同的关注点分离,而不是容器组件
- 让展示组件保持无状态——它们只通过 props 接收数据
- 使用
useDogImages()组合式函数模式,作为容器包装器的现代替代方案
详情
虽然这种模式主要与 React 相关联,但它的基本原则已在其他库和框架中以各种形式得到采用和改编。
Dan 的区分为 JavaScript 应用提供了一种更清晰、更具扩展性的组织方式。通过明确界定不同类型组件的职责,开发者可以确保 UI 组件(展示组件)和逻辑(容器组件)具有更好的复用性。
然而,随着 React 中 hooks 和 Vue 3 中组合式 API 的出现,展示组件与容器组件之间的明确界限开始变得模糊。Hooks 和组合式 API 让开发者能够封装并复用状态和逻辑,而不必局限于基于类的容器组件或选项式 API。即便如此,这种模式在某些时候仍然有所帮助。
假设我们想创建一个应用,获取 6 张狗狗图片,并将这些图片渲染到屏幕上。
为了遵循容器/展示组件模式,我们希望通过将这个过程分成两部分来实现关注点分离:
- 展示组件:关注向用户展示_什么_数据。在这个示例中,就是渲染狗狗图片列表。
- 容器组件:关注向用户展示数据的_方式_。在这个示例中,就是获取狗狗图片。
获取狗狗图片涉及应用逻辑,而显示图片只涉及视图。
展示组件
展示组件通过 props 接收数据。它的主要作用只是按照我们期望的方式显示接收到的数据,包括样式,_而不修改_这些数据。
渲染狗狗图片时,我们只需遍历从 API 获取的每张狗狗图片,并渲染这些图片。我们可以创建一个 DogImages 组件,通过 props 接收数据,并渲染收到的数据。
<template>
<div>
<div v-for="(dog, index) in dogs" :key="index">
<img :src="dog" alt="Dog" />
</div>
</div>
</template>
<script setup>
defineProps(["dogs"]);
</script>
DogImages 组件是一个展示组件。展示组件_通常_是无状态的:它们不包含自己的 Vue 状态,除非出于 UI 目的需要状态。展示组件从容器组件接收数据。
容器组件
容器组件的主要作用是将数据传递给它们所_包含_的展示组件。容器组件本身通常不会渲染其他组件,只会渲染需要使用其数据的展示组件。由于它们本身不渲染任何内容,通常也不包含任何样式。
我们需要创建一个容器组件来获取这些数据,并将数据传递给展示组件 DogImages,以便在屏幕上显示。
<template>
<DogImages :dogs="dogs" />
</template>
<script setup>
import { ref, onMounted } from "vue";
import DogImages from "./DogImages.vue";
const dogs = ref([]);
onMounted(async () => {
const response = await fetch(
"https://dog.ceo/api/breed/labrador/images/random/6"
);
const { message } = await response.json();
dogs.value = message;
});
</script>
将这两个组件结合起来,就可以将应用逻辑与视图分离。
组合式函数
在很多情况下,组合式函数可以取代容器/展示模式。Composition API 的引入让开发者可以轻松地添加状态,而无需通过容器组件来提供状态。
我们可以创建一个自定义组合式函数来获取图片,并返回狗狗数组,而不是将数据获取逻辑放在容器组件中。
import { ref, onMounted } from "vue";
export function useDogImages() {
const dogs = ref([]);
onMounted(async () => {
const response = await fetch(
"https://dog.ceo/api/breed/labrador/images/random/6"
);
const { message } = await response.json();
dogs.value = message;
});
return { dogs };
}
使用这个组合式函数后,我们不再需要用外层容器组件来获取数据。相反,我们可以直接在展示组件 DogImages 中使用这个组合式函数!
<template>
<div>
<div v-for="(dog, index) in dogs" :key="index">
<img :src="dog" alt="Dog" />
</div>
</div>
</template>
<script setup>
import { useDogImages } from "../composables/useDogImages";
const { dogs } = useDogImages();
</script>
通过使用 useDogImages 组合式函数,我们仍然将应用逻辑与视图分离了。我们只使用组合式函数返回的数据,不在组件中修改这些数据。
组合式函数可以像容器/展示模式一样,轻松地将组件中的逻辑与视图分离。它省去了原本用于将展示组件包裹在容器组件中的额外层级。