目录结构看似是小事,实际上它决定了项目的长期可维护性。不少团队在项目初期随手建了一个components文件夹,把所有组件一股脑扔进去,等到项目迭代一年后,这个文件夹里躺着两百多个文件,找一个Modal组件要靠搜索,改一个功能要在五六个目录之间来回跳转。这篇文章就来聊聊React社区讨论最多的两种组织方式:Feature-First(按功能组织)和Component-First(按组件类型组织),分析它们各自的适用场景,并给出可落地的实践建议。

Component-First:按技术类型分层的传统方案
Component-First是最容易想到的组织方式,按照文件的技术职责划分目录。它的典型结构如下:
src/ ├── components/ │ ├── Button.jsx │ ├── Modal.jsx │ └── UserCard.jsx ├── hooks/ │ ├── useAuth.js │ └── useFetch.js ├── services/ │ └── api.js ├── utils/ │ └── format.js ├── pages/ │ ├── Home.jsx │ └── Profile.jsx └── App.jsx
这种结构的核心逻辑是“同类文件放一起”,所有的展示组件进components,所有的自定义Hook进hooks,所有的接口请求进services。对于刚入门的开发者来说,这种划分方式认知成本最低,因为目录名直接对应了文件的技术类型,看到hooks文件夹就知道里面是什么。
它的优势在小项目里非常明显。当组件总数不超过三五十个时,按类型分层能让代码位置一目了然,团队新成员几分钟就能摸清项目全貌。而且公共组件天然集中,比如你写了一个通用的Button,其他页面直接从components/Button引入即可,复用路径很短。
但项目规模一旦上来,问题就暴露了。假设你在开发“用户资料编辑”功能,相关的文件散落在四个目录里:组件在components、状态在hooks、请求在services、页面在pages。修改一个功能要在四个文件夹之间来回切换,删除一个功能更是要小心翼翼地在各个目录里找残留文件。时间一长,没人敢确定某个组件是不是只有一处引用,代码就慢慢变成了“不敢删的死代码堆”。
Feature-First:按业务功能聚合的现代实践
Feature-First的思路正好相反:以业务功能为单位组织代码,每个功能模块内部包含自己需要的一切。结构大致是这样:
src/ ├── features/ │ ├── auth/ │ │ ├── components/ │ │ │ ├── LoginForm.jsx │ │ │ └── RegisterForm.jsx │ │ ├── hooks/ │ │ │ └── useAuth.js │ │ ├── services/ │ │ │ └── authApi.js │ │ └── index.js │ ├── profile/ │ │ ├── components/ │ │ │ ├── ProfileEditor.jsx │ │ │ └── AvatarUploader.jsx │ │ ├── hooks/ │ │ │ └── useProfile.js │ │ └── index.js │ └── orders/ │ ├── components/ │ │ └── OrderList.jsx │ └── index.js ├── components/ # 真正的跨功能公共组件 │ ├── Button.jsx │ └── Modal.jsx └── App.jsx
注意每个功能模块都导出一个index.js作为对外入口,外部只能通过这个入口访问功能内部的文件。这个约束非常关键,它形成了模块边界。其他模块引用登录功能时写import { LoginForm } from './features/auth',而不是直接深入到features/auth/components/LoginForm。路径约束带来了两个好处:一是内部实现可以随时重构而不用担心影响外部,二是依赖关系清晰,删除整个功能文件夹就能干净地移除一个业务模块。
Feature-First的另一个隐性收益是团队协作效率。按功能划分后,负责订单模块的同学日常改动几乎都发生在features/orders目录内,与其他人的代码交集极少,Git合并冲突自然减少。代码审查时也更容易判断一个改动是否越界——如果订单模块的代码出现在auth目录的引用里,审查者一眼就能发现异常依赖。
当然它也有代价。首先是目录层级变深,嵌套三四层是常态,相对路径容易写成../../../components/Button这种形式,通常需要配合路径别名(如@/components/Button)来缓解。其次是判断“什么算公共组件”会引发争议:一个组件今天只有一个功能用它,明天另一个功能也要用,什么时候该把它提升到顶层components目录?这需要团队约定清晰的规则,比如“被两个以上功能引用时才提升”。
两种方案的对比与选型建议
把两种方案的关键维度放在一起对比会更直观:
| 维度 | Component-First | Feature-First |
|---|---|---|
| 上手成本 | 低,目录含义直白 | 中,需要理解模块边界概念 |
| 功能内聚性 | 差,相关文件分散 | 好,功能自包含 |
| 删除功能 | 困难,需多处清理 | 容易,删目录即可 |
| 公共组件复用 | 天然集中 | 需要提升机制 |
| 适合规模 | 小型项目、原型 | 中大型、长期维护项目 |
选型时可以参考几个信号。如果项目生命周期预计不超过半年、组件数量在五十个以内、团队只有两三个人,Component-First完全够用,没必要为了“架构正确”增加复杂度。反之,如果项目要长期迭代、有多个团队并行开发、业务模块边界清晰(电商的项目天然有商品、订单、购物车这些功能),Feature-First的投入会在三到六个月后开始持续产生回报。
实际工程中还有一种务实的混合策略:顶层保留features目录承载业务模块,同时保留components、hooks、utils存放真正的基础设施代码。迁移也不必一步到位,可以从最复杂的那个功能开始,把它的相关文件聚合到features下,逐步推进。配合ESLint的import/no-restricted-paths规则,还能在工具层面强制执行模块边界,防止有人绕过入口直接引用内部文件。
最后提醒一句,目录结构没有银弹,比结构本身更重要的是团队对结构的共识。把规则写进项目的README,在代码评审中坚持执行,再合理的架构才能发挥价值。
React目录结构Feature-First组件化架构修改时间:2026-09-05 12:16:33