# 前端开发规范
> React 18 + Ant Design v6 · 适用于 LLM 辅助代码重构
---
## 目录结构
```
src/
├── api/ # HTTP 请求层
├── types/ # TypeScript 类型定义
├── hooks/ # 业务逻辑 & 异步状态
├── services/ # 复杂业务逻辑(可选,简单项目可省略)
├── stores/ # 全局状态(Zustand / Jotai)
├── components/ # 通用 UI 组件(无业务逻辑)
├── features/ # 业务功能模块(胶水层)
├── pages/ # 路由入口(薄层)
├── layouts/ # 页面框架
└── utils/ # 纯工具函数
```
---
## 各层职责
| 目录 | 应该放 | 不应该放 |
| ------------- | -------------------------------------------------------------- | -------------------------------------- |
| `layouts/` | 页面框架、导航、侧边栏、Header/Footer、Outlet | 业务逻辑、API 调用、具体页面内容 |
| `pages/` | 路由入口、路由参数提取、权限守卫、页面 title、组合 features | 业务逻辑、UI 细节、直接调 API |
| `features/` | 连接 hooks 与 components、业务交互回调、局部状态(筛选条件等) | 纯 UI 样式、直接 fetch、跨模块共享逻辑 |
| `components/` | 纯 UI 组件、样式、props 驱动的交互 | API 调用、业务判断、store 依赖 |
| `api/` | HTTP 请求函数、请求/响应结构映射、拦截器 | 业务逻辑、UI 反馈、缓存管理 |
| `hooks/` | 异步状态管理、业务逻辑封装、跨组件共享状态 | JSX 渲染、直接操作 DOM、样式 |
| `services/` | 复杂业务计算、多个 api 组合调用、领域逻辑 | UI 反馈、React 相关代码 |
| `types/` | 全局共享的 TS 类型、接口定义、枚举 | 逻辑代码、默认值、工具函数 |
| `utils/` | 纯函数工具(格式化、校验、日期处理) | 副作用、API 调用、React 代码 |
---
## 数据流向(单向)
```
用户操作
→ features/ 触发
→ hooks/ 处理逻辑
→ api/ 发请求
→ 响应回 hooks/(缓存/状态更新)
→ features/ 重渲染
→ components/ 展示结果
```
---
## components 与 features 的区别
**判断标准是与业务是否耦合,而不是复杂度。**
| 维度 | `components/` | `features/` |
| -------- | -------------------- | ----------------------- |
| 判断标准 | 不知道业务是什么 | 知道具体业务是什么 |
| 数据来源 | 只接收 props | 自己调 hooks 取业务数据 |
| 可复用性 | 跨项目可复用 | 只在本项目有意义 |
| 复杂度 | 不限(可以非常复杂) | 不限 |
```tsx
// ✅ components/ — 不知道「课程」是什么业务概念,换个项目也能用
;
// ✅ features/ — 知道业务,负责注入业务回调
const CourseVideoPlayer = ({ lessonId }) => {
const { attachmentId } = useLesson(lessonId); // 业务
const { markCompleted } = useLessonProgress(lessonId); // 业务
return ;
};
```
> **原则**:`components/` 通过 props 暴露关键事件(`onEnded`、`onProgress`),`features/` 负责注入业务逻辑,保持 `components/` 干净可复用。
---
## hooks 放置规则
**核心问题:这个 hook 会不会在这个组件之外被用到?**
| 情况 | 放哪里 |
| -------------------------------------------------------- | ------------ |
| 封装了业务 API 调用 | `src/hooks/` |
| 多个不相关的地方都会用 | `src/hooks/` |
| 只服务于某个组件族,外部不会用 | 组件目录里 |
| 依赖组件内部 ref 或第三方库特定实例(playerRef、mapRef) | 组件目录里 |
```text
src/hooks/
└── useAttachment.ts ✅ 任何地方都可能用,与具体组件无关
components/VideoPlayer/core/
├── useVideoPlayer.ts ✅ 依赖 video.js 实例,强绑定 VideoPlayer 内部
└── useVideoControls.ts ✅ 依赖 playerRef,离开组件没有意义
```
---
## 错误处理分层
```text
api 层 抛出结构化错误,不处理 UI
hook onError 处理业务特定错误(该 feature 内有特殊含义的错误码)
组件层 try/catch 处理仅影响当前组件交互的错误(如表单校验)
```
**决策树:**
```text
收到错误
├── 所有页面都一样处理?(401/403/500)
│
└── 只在这个业务场景特殊处理?
├── 影响整个 feature 的逻辑 → hook 的 onError
└── 只影响当前组件交互 → 组件内 try/catch
```
**⚠️ antd v6 notification 问题**:`App.useApp()` 取到的实例无法在 QueryClient 回调中直接使用,需挂载单例:
```ts
// utils/antdStatic.ts
let _notification: NotificationInstance;
export const setNotification = (n: NotificationInstance) => {
_notification = n;
};
export const staticNotification = {
error: (args) => _notification?.error(args),
};
// layouts/AppInitializer.tsx(在 内部)
const { notification } = App.useApp();
useEffect(() => {
setNotification(notification);
}, []);
```
---
## Ant Design v6 适配要点
**根节点配置:**
```tsx
// main.tsx
```
**注意事项:**
- 使用 `App.useApp()` 替代 `message.xxx` / `Modal.confirm`(v6 静态方法已弃用)
- Form 数据保持在 Form 实例内,只有提交结果才流向业务层
- 复杂表格的 `columns` 单独抽成 `columns.tsx`,保持组件干净
- `ConfigProvider` 放顶层,统一管理 token、locale、theme
---
## 推荐技术栈
| 职责 | 方案 |
| ------------ | ------------------- |
| 全局同步状态 | redux |
| 路由 | React Router v7 |
| HTTP 客户端 | fetch token、错误) |
| UI 组件库 | Ant Design v6 |
---
## 一句话总结
| 层 | 定义 |
| ------------- | -------------------------------------- |
| `api/` | 只管收发 HTTP,不含任何判断 |
| `hooks/` | 承载状态管理、缓存、错误处理,不含 JSX |
| `services/` | 纯业务计算,与 UI/React 完全无关 |
| `components/` | 纯 UI,props 驱动,可跨项目复用 |
| `features/` | 胶水层,把 hooks 的数据喂给 components |
| `pages/` | 路由入口,组合 features,越薄越好 |
| `layouts/` | 页面框架壳子,不知道任何业务 |
| `stores/` | 只存真正需要跨模块共享的状态 |
| `utils/` | 纯函数,无副作用,无 React |