React 大型项目目录骨架
目标:建立可扩展的目录直觉:
features/pages/shared,知道新代码该往哪放。
引言
入门 demo 往往是:
text
src/
App.tsx
components/
Header.tsx
TodoList.tsx
UserCard.tsx
...
项目变大以后,问题不是「React 不会写」,而是:
- 改一个业务,要在
components/里翻半天才找到相关文件 - 首页、登录、任务模块的组件混在一起,互相乱引用
- 新人不知道「这个按钮 / 这个 hook 该放哪」
本篇不追完美架构,只给一套够用、可长 的起步骨架:按 业务(feature) 内聚,页面保持薄,跨业务的东西进 shared。
本篇 demo/ 已按此拆了一版可点的迷你应用:首页 + 任务模块 + 共享按钮。
目录示意

推荐起步结构:
text
src/
app/ # 可选:全局 provider、路由表
pages/ # 路由级页面(薄)
features/ # 按业务内聚(tasks、auth...)
shared/ # 跨业务 UI / utils
动手
完整代码:react2/08-large-project/demo
bash
cd react2/08-large-project/demo
npm install
npm run dev
/→pages/HomePage/tasks→features/tasks/TasksPage(用shared/Button)
点顶栏「首页 / 任务」,感受「路由页」和「业务模块」是怎么分开的。
1. 为什么不要一股脑塞进 components/
| 做法 | 短期 | 项目长大以后 |
|---|---|---|
全部丢 components/ |
好找文件名 | 业务边界糊掉,互相 import 成蜘蛛网 |
按 UI 类型分(buttons/、forms/) |
看起来整齐 | 改「任务」仍要跨很多目录 |
按 feature 分(features/tasks/) |
多建一层目录 | 相关代码挨在一起,改一块少翻别处 |
直觉:经常一起改的,放一起 。
「任务列表 + 加任务逻辑 + 任务相关 hook」属于 features/tasks;「全站都能用的按钮」才进 shared。
2. 四层分别干什么
pages/:路由级页面(尽量薄)
对应 URL 入口,少写业务细节,多做组装:
tsx
// pages/HomePage.tsx
import { Link } from 'react-router-dom'
export function HomePage() {
return (
<section className="panel">
<h1>首页</h1>
<p>
大型项目按 <code>features / pages / shared</code> 拆目录,功能内聚、页面薄。
</p>
<Link to="/tasks">进入任务模块 →</Link>
</section>
)
}
约定:
- 一个路由 ≈ 一个 page(或 page 再调 feature)
- page 里少堆
useState/ 复杂请求;重逻辑下沉到features/
features/:按业务内聚
一个业务一块地:页面、组件、hooks、API 都可以放在同一 feature 下。
tsx
// features/tasks/TasksPage.tsx
import { useState } from 'react'
import { Button } from '../../shared/Button'
export function TasksPage() {
const [items, setItems] = useState(['搭建目录', '拆分 feature', '接入路由'])
return (
<section className="panel">
<h1>任务 Feature</h1>
<ul>
{items.map((item) => (
<li key={item}>{item}</li>
))}
</ul>
<Button
onClick={() =>
setItems((list) => [...list, `新任务 ${list.length + 1}`])
}
>
添加任务
</Button>
</section>
)
}
长大以后可以继续往下拆,例如:
text
features/tasks/
TasksPage.tsx
components/TaskItem.tsx
hooks/useTasks.ts
api.ts
先别过度拆;文件变多、找起来费劲时再拆。
shared/:跨业务复用
只放「两个及以上 feature 都会用」的东西:通用 Button、布局、日期格式化、fetch 封装等。
tsx
// shared/Button.tsx
import type { ButtonHTMLAttributes, ReactNode } from 'react'
type Props = ButtonHTMLAttributes<HTMLButtonElement> & {
children: ReactNode
}
export function Button({ children, ...rest }: Props) {
return (
<button type="button" className="btn" {...rest}>
{children}
</button>
)
}
判断口诀:
- 只有任务模块用 → 留在
features/tasks - 首页、任务、设置都会用 →
shared
app/(可选):全局装配
Provider(QueryClient、主题)、路由表、鉴权壳子等「应用级胶水」可以放这里。
本 demo 体量小,路由直接写在 App.tsx,没有单独建 app/,这是刻意简化。
3. 路由怎么把它们串起来
tsx
// App.tsx
import { BrowserRouter, Link, Route, Routes } from 'react-router-dom'
import { TasksPage } from './features/tasks/TasksPage'
import { HomePage } from './pages/HomePage'
export default function App() {
return (
<BrowserRouter>
<div className="shell">
<header className="top">
<strong>LargeApp 骨架</strong>
<nav>
<Link to="/">首页</Link>
<Link to="/tasks">任务</Link>
</nav>
</header>
<main>
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="/tasks" element={<TasksPage />} />
</Routes>
</main>
</div>
</BrowserRouter>
)
}
依赖方向可以记成:
text
App(路由 / 壳)
→ pages / features(页面与业务)
→ shared(通用 UI / utils)
尽量避免:shared 去 import 某个 features/xxx(通用层不应依赖具体业务)。
4. 对照表
| 目录 | 回答的问题 | demo 里有什么 |
|---|---|---|
pages/ |
这个 URL 入口是谁? | HomePage |
features/tasks/ |
任务业务的代码在哪? | TasksPage + 本地 state |
shared/ |
全站通用控件? | Button |
App.tsx |
壳子 + 路由表 | 顶栏 + /、/tasks |
| 新增需求 | 优先落点 |
|---|---|
| 新路由「关于页」 | pages/AboutPage + 挂一条 Route |
| 任务详情、任务筛选 | features/tasks/ 里加文件 |
全站统一的 Input |
shared/Input.tsx |
| 登录 / 权限一整块 | features/auth/(不要散到各处) |
5. 常见坑
| 做法 | 问题 |
|---|---|
一切仍丢 components/ |
目录「看起来有结构」,业务边界没有 |
| page 里写满请求与业务规则 | 页面变胖,难复用、难测 |
| feature 之间互相乱引用 | tasks ↔ auth 缠死,拆不动 |
过早建 entities / widgets 七层 |
小项目被架构压垮;先 features + shared 够用 |
把只属于任务的按钮放进 shared |
shared 膨胀成杂物间 |
demo 在练什么
- 用
pages/features/shared看清三层职责 - 路由只负责挂载,业务状态留在 feature 内
- feature 通过
shared/Button复用通用 UI,而不是复制一份样式按钮
自己改一改:再加一个 features/notes/,挂 /notes;或把任务列表拆成 TaskItem.tsx 仍放在 features/tasks/ 下。
运行截图
