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

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

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

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

用户
UI 界面
组件1
组件2
组件3
容器1
容器2
接口1
接口2
Redux
接口3
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} />;
};
JSX

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

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>;
};
TSX

现代最佳实践建议

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

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


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