组件是否应该访问数据源?

组件是否应该访问数据源?

在 react 早期,redux 刚刚开始流行的时候,通常组件会分别存在 containercomponent 文件夹下。

container 我们会访问 api、redux 或 localstorage 等外部数据源,而 component 只能接受 props 参数。

graph TD
    USER[用户] --> UI[UI 界面]
    UI --> COMPNENT_1[组件1]
    UI --> COMPNENT_2[组件2]
    UI --> COMPNENT_3[组件3]
    COMPNENT_1 --> CONTAINER_1[容器1]
    COMPNENT_2 --> CONTAINER_2[容器2]
    COMPNENT_3 --> CONTAINER_2
    CONTAINER_1 --> API_1[接口1]
    CONTAINER_1 --> API_2[接口2]
    CONTAINER_1 --> REDUX[Redux]
    CONTAINER_2 --> API_3[接口3]
    CONTAINER_2 --> LOCAL_STORAGE[localStorage]

但是到了今天,zustand、Jotai 等原子化设计的状态管理库、swr 这种利用 hooks 封装请求的库大行其道。项目中没有了 container,大家都非常随意的在 component 里面访问接口、全局变量、zustand store、redux 等等。

这点到底好还是不好呢?

历史背景与技术约束

首先我们要知道早期没有 hooks 的时候,React 接入 redux 是需要使用 hoc 的。Redux 有自己的强约束性:要求通过 connect 高阶组件接入 store。

自然形成了容器组件作为“中间层”的架构。

技术演进带来的范式转变

在 react 16.8 之后,函数组件和 hooks 成为主流,useState/useEffect 等 API 模糊了组件层级边界。

这种背景下,redux 也不需要 connect 高阶组件了,直接 useDispatch useSelector 就可以在组件中使用,后面也衍生出 Zustand/Jotai 等更方便简单的全局数据管理库。

甚至于还有 swr 这种项目,让我们能直接在多个组件中直接调用 useUser,它内部会自动帮我们处理——不管在多少组件中都用了 useUser,都只会进行一次请求。相当于直接同时解决了全局状态管理、请求结果缓存、请求去重等多个问题。

现代实践的利弊分析

✅ 优势:

  1. 开发体验提升:减少文件跳转,逻辑更内聚
  2. 更灵活的代码组织:逻辑与 UI 可以按功能而非类型组织
  3. 更适合现代 SSR/SSG:数据获取与组件更紧密集成

❌ 风险: 4. 组件熵增:单个组件可能混杂 UI/逻辑/副作用 5. 测试复杂度:需要更多 mock 和集成测试 6. 可重用性下降:业务耦合度高的组件难以复用 7. 追踪困难:数据流在组件树中更分散

架构选择的平衡之道

小型项目:允许在组件内直接访问状态

1
2
3
4
5
6
// 推荐模式:逻辑通过自定义Hook封装
const UserProfile = () => {
const { user, loading } = useUserProfile(); // 自定义Hook封装API/状态访问
if (loading) return <Spinner />;
return <ProfileCard avatar={user.avatar} />;
};

大型项目:显式分层依然必要

1
2
3
4
5
6
7
8
9
10
// containers/UserProfileContainer.tsx
const UserProfileContainer = () => {
const user = useFetchUser();
return <UserProfile user={user} />;
};

// components/UserProfile.tsx
const UserProfile = ({ user }: Props) => {
return <Card>{user.name}</Card>;
};

现代最佳实践建议

  • 逻辑收敛原则:即使不显式分层,也要通过自定义 Hooks 收敛副作用
  • 组件纯度分级
    • Level 1: 纯 UI 组件 (禁止任何外部访问)
    • Level 2: 轻度业务组件 (允许访问上下文)
    • Level 3: 业务容器组件 (允许完整副作用)
  • 架构守护方案
    • 通过 ESLint 规则限制某些目录下的组件不能直接访问 store
    • 使用分层测试策略(单元测试/集成测试/E2E 测试的比例分配)

总结来看,行业惯例的转变本质是开发效率与架构严谨性的动态平衡。关键不在于是否使用 container/component 模式,而是能否建立适合当前团队和项目阶段的约束规则。对于追求长期维护性的项目,建议保留某种形式的分层约定,但实现方式可以更现代化(如通过 Hooks 分层而非文件类型分层)。


组件是否应该访问数据源?
https://www.hangyu.art/2025-02-15/组件是否应该访问数据源/
作者
徐航宇
发布于
2025年2月15日
许可协议