235 lines
9.6 KiB
Markdown
235 lines
9.6 KiB
Markdown
|
|
# 前端代码风格与架构规范(Frontend Code Style)
|
|||
|
|
|
|||
|
|
本文档面向 TraceStudio 前端开发者与 AI 助手,定义在 `web/` 下的代码风格、目录与职责分配、面向对象与函数式的使用建议、测试规范、以及代码评审/提交约定。目标是:保持一致性、可读性、可维护性,并支持节点式分析平台的扩展性。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 一、总体原则
|
|||
|
|
|
|||
|
|
- 以可组合(composition)为首选:优先使用函数式 React 组件 + Hooks;避免复杂继承结构。面向对象用于领域模型、服务与工具(非 React UI)。
|
|||
|
|
- 协议优先于实现:优先定义接口(TypeScript types / interfaces),节点、API 与组件通过契约(schema)通信而非耦合实现细节。
|
|||
|
|
- 原子化与单一职责:组件应尽量小、单一、易测试。复杂视图由小组件组合而成(Container/Presentational 分离或 Hook 提取)。
|
|||
|
|
- 明确分层:UI 组件(pure)→ 容器/页面(组合)→ 状态层(Zustand)→ API 层(`src/utils/api.ts`)→ 后端。
|
|||
|
|
- 防御性编程:输入校验、错误边界(Error Boundary)、与用户友好的错误提示。
|
|||
|
|
- 可测试优先:关键逻辑与交互需附带单元/集成测试。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 二、技术栈约定(回顾)
|
|||
|
|
|
|||
|
|
- React 19 + TypeScript 5.x(强制开启 `strict`)
|
|||
|
|
- Zustand(全局/共享状态)
|
|||
|
|
- React Flow(节点画布)
|
|||
|
|
- Vite(构建)
|
|||
|
|
- Tailwind CSS(样式) + 可选 shadcn/ui 风格组件
|
|||
|
|
- 测试:Vitest + React Testing Library;E2E 推荐 Playwright
|
|||
|
|
- Lint/格式化:ESLint + Prettier + TypeScript ESLint rules
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 三、目录与职责(放哪里?)
|
|||
|
|
|
|||
|
|
在 `web/src/` 下按以下约定组织文件:
|
|||
|
|
|
|||
|
|
- `components/`:可复用的 UI 组件(按功能或页面子目录组织)
|
|||
|
|
- 每个组件使用独立目录:`components/MyComp/` 包含 `index.tsx`、`MyComp.test.tsx`、`styles.css`(如需)、`types.ts`(组件私有 types)
|
|||
|
|
- `components/nodes/`:React Flow 的节点自定义组件(每个节点一个文件)
|
|||
|
|
- `stores/`:Zustand store(按 domain 分文件,如 `useStore.ts`, `userStore.ts`)
|
|||
|
|
- `utils/`:工具函数、类型转换、`api.ts`(与后端请求封装)、`nodeRegistry.ts`(节点注册逻辑)
|
|||
|
|
- `pages/` 或 `views/`:高阶页面/路由级容器(如 Workspace 页面)
|
|||
|
|
- `hooks/`:自定义 Hook(业务通用逻辑,便于复用)
|
|||
|
|
- `assets/`:静态资源(图标、示例文件)
|
|||
|
|
- `styles/`:全局样式与 Tailwind 配置(tailwind.css)
|
|||
|
|
- `tests/`:大规模集成或 e2e 脚本(可选)
|
|||
|
|
- `types/`:全局共享类型定义(如 `node-types.ts`、`api-types.ts`)
|
|||
|
|
|
|||
|
|
规则举例:
|
|||
|
|
- 组件目录示例:
|
|||
|
|
- `src/components/FileExplorer/index.tsx`
|
|||
|
|
- `src/components/FileExplorer/FileExplorer.test.tsx`
|
|||
|
|
- `src/components/FileExplorer/types.ts`
|
|||
|
|
|
|||
|
|
- 节点组件示例:
|
|||
|
|
- `src/components/nodes/CSVLoader/index.tsx`
|
|||
|
|
- `src/components/nodes/CSVLoader/preview.tsx`(可选)
|
|||
|
|
- `src/components/nodes/CSVLoader/types.ts`
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 四、命名与导出规范
|
|||
|
|
|
|||
|
|
- 文件名:`kebab-case` 或 `PascalCase` 视语言习惯;组件文件夹使用 PascalCase(`MyComponent`),文件导出使用 `index.tsx` 导出默认组件。
|
|||
|
|
- 组件名:PascalCase,例如 `FileExplorer`、`HeaderBar`。
|
|||
|
|
- Type / Interface:后缀使用 `PascalCase`,接口前缀不强制 `I`(推荐不使用),例如 `NodeMeta`, `FileInfo`。
|
|||
|
|
- 常量:UPPER_SNAKE_CASE 或 PascalCase 视上下文(环境变量 UPPER,UI 常量 PascalCase)。
|
|||
|
|
- Hooks:以 `use` 前缀,例如 `useFileLoader`。
|
|||
|
|
- Store:以 `use` 前缀,例如 `useStore`、`useUserStore`。
|
|||
|
|
|
|||
|
|
导出风格:
|
|||
|
|
- 组件默认导出:`export default FileExplorer`(便于懒加载)
|
|||
|
|
- 类型与工具使用具名导出:`export type { NodeMeta }`,`export function formatDate()`
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 五、组件设计规范(React)
|
|||
|
|
|
|||
|
|
1. 优先使用函数组件 + Hooks。
|
|||
|
|
2. 组件职责:小、单一、可复用;复杂交互拆为子组件与 Hook。
|
|||
|
|
3. Props 类型:显式声明 `type Props = { ... }` 并 `React.FC<Props>` 或 `function Comp(props: Props)`。
|
|||
|
|
4. 不要在组件内部进行副作用外的复杂业务逻辑——将其抽到 Hook 或 store。
|
|||
|
|
5. 尽量无状态(presentational)组件与有状态容器(container)分离。
|
|||
|
|
6. 使用 `ErrorBoundary` 包裹可能抛错的区域(如渲染外部数据)并显示用户友好错误。
|
|||
|
|
7. 可访问性(a11y):可键盘操作、SVG 加上 `aria-*`、语义化标签。
|
|||
|
|
|
|||
|
|
示例组件骨架:
|
|||
|
|
|
|||
|
|
```tsx
|
|||
|
|
// src/components/MyWidget/index.tsx
|
|||
|
|
import React from 'react'
|
|||
|
|
import type { MyWidgetProps } from './types'
|
|||
|
|
|
|||
|
|
export default function MyWidget(props: MyWidgetProps) {
|
|||
|
|
const { title, data } = props
|
|||
|
|
return (
|
|||
|
|
<div className="my-widget">
|
|||
|
|
<h3>{title}</h3>
|
|||
|
|
{/* 渲染 */}
|
|||
|
|
</div>
|
|||
|
|
)
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 六、面向对象(OOP)使用指导
|
|||
|
|
|
|||
|
|
- 在前端工程中,OOP 适用于:
|
|||
|
|
- 业务模型(domain models)和复杂数据结构(例如:TraceFile、ProfileSummary)。
|
|||
|
|
- 服务类(与后端交互的封装,如 `class FileService { upload(){} }`),特别是当有内部状态/连接需要封装时。
|
|||
|
|
- 禁忌:不要为了使用类而用类来实现 UI 组件(React 已以函数式为主)。
|
|||
|
|
- 推荐模式:小型无状态类 + 通过接口注入(依赖反转),便于在测试中替换实现。
|
|||
|
|
|
|||
|
|
示例(服务类):
|
|||
|
|
|
|||
|
|
```ts
|
|||
|
|
// src/utils/FileService.ts
|
|||
|
|
export class FileService {
|
|||
|
|
constructor(private baseUrl: string) {}
|
|||
|
|
async upload(file: File) { /* ... */ }
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 七、状态管理(Zustand)约定
|
|||
|
|
|
|||
|
|
- 所有应用级状态放在 `src/stores/`,按 domain 拆分(`useStore.ts` 为全局工作流状态,`useUserStore.ts` 单独管理用户信息)。
|
|||
|
|
- Store 只包含状态与同步/异步的变更方法(actions);避免在 store 中做 DOM 操作或复杂渲染逻辑。
|
|||
|
|
- Store 的方法应返回 Promise(如有异步),便于调用方捕获错误与链式处理。
|
|||
|
|
|
|||
|
|
示例:
|
|||
|
|
|
|||
|
|
```ts
|
|||
|
|
// src/stores/useStore.ts
|
|||
|
|
export const useStore = create((set, get) => ({
|
|||
|
|
nodes: [],
|
|||
|
|
setNodes: (nodes) => set({ nodes }),
|
|||
|
|
loadWorkflow: async (path) => { /* await fetch... */ }
|
|||
|
|
}))
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 八、API 层与后端契约
|
|||
|
|
|
|||
|
|
- 所有后端请求通过 `src/utils/api.ts` 封装为高层方法(`getPlugins()`, `executeGraph(payload)` 等)。
|
|||
|
|
- API 返回应统一为 `{ data?: any, error?: string }`,前端统一解析该结构。
|
|||
|
|
- 在文档中声明关键端点与示例请求/响应(见下文 API 规范草案建议)。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 九、节点插件与自定义节点约定
|
|||
|
|
|
|||
|
|
- 节点元数据接口(示例):
|
|||
|
|
|
|||
|
|
```ts
|
|||
|
|
export type NodeMetadata = {
|
|||
|
|
display_name: string
|
|||
|
|
category: string
|
|||
|
|
inputs: Array<{ name: string; type: string }>
|
|||
|
|
outputs: Array<{ name: string; type: string }>
|
|||
|
|
params?: Record<string, any>
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- 前端 `nodeRegistry` 应暴露组件映射与 `meta`,并以 `GET /api/plugins` 为数据源。如果用户上传自定义节点并注册,后端应返回插件列表更新。
|
|||
|
|
- 前端对自定义节点仅渲染元数据描述与 UI 表单;执行逻辑由后端驱动或以安全沙箱运行。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 十、错误处理与日志
|
|||
|
|
|
|||
|
|
- 所有异步 API 调用必须有错误处理(try/catch),并返回友好错误到 UI(非控制台)。
|
|||
|
|
- 在关键组件使用 `try { } catch (err) { setError(err.message) }` 并在 UI 显示可复制的错误信息和“定位至节点”或“查看详细日志”按钮。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 十一、测试规范
|
|||
|
|
|
|||
|
|
- 单元/组件测试:Vitest + React Testing Library
|
|||
|
|
- 文件命名:`Xxx.test.ts` / `Xxx.test.tsx`
|
|||
|
|
- 覆盖核心逻辑与交互,包括:NodePalette 渲染、拖拽行为(可模拟 dataTransfer)、Inspector 参数绑定、FileExplorer 文件操作(mock API)。
|
|||
|
|
- 断言样式:优先使用可见文本、ARIA 查询与行为断言(而非实现细节)。
|
|||
|
|
- 集成测试:对关键业务流(导入文件 → 创建节点 → 预览 → 运行)编写集成用例。
|
|||
|
|
- E2E:Playwright(建议用于文件拖放、跨窗口文件预览、上传流程)
|
|||
|
|
- 覆盖率门槛:建议核心模块覆盖率 >= 70%,node/plugins 关键逻辑 >= 80%(可视团队资源调整)。
|
|||
|
|
|
|||
|
|
运行命令示例:
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
# 在 web/ 目录
|
|||
|
|
npm run test # 运行单元测试(vite/vitest)
|
|||
|
|
npm run test:watch # 开发时监视测试
|
|||
|
|
npm run test:e2e # (若配置) 运行 e2e
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 十二、Lint / 格式化 / 提交规范
|
|||
|
|
|
|||
|
|
- ESLint + Prettier:在 PR 前执行 `npm run lint` 与 `npm run format`。
|
|||
|
|
- 代码风格:开启 TypeScript `strict`、避免 `any`(必要时使用注释说明)。
|
|||
|
|
- Commit 信息:使用 Conventional Commits(`feat:`、`fix:`、`chore:`、`docs:`等)。
|
|||
|
|
- PR 模板:每个 PR 需包含变更摘要、测试说明、影响范围、回滚/兼容性注意事项。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 十三、Code Review Checklist(最少项)
|
|||
|
|
|
|||
|
|
- 代码是否小且聚焦单一职责?
|
|||
|
|
- 有无单元/集成测试覆盖关键路径?
|
|||
|
|
- 类型是否明确(避免 any)?
|
|||
|
|
- 是否保持了 UI/UX 与现有组件风格一致?
|
|||
|
|
- 是否包含必要的错误处理与用户提示?
|
|||
|
|
- 是否有性能或安全问题(例如不受限的文件读写、直接 eval)?
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 十四、示例:将新功能放在哪儿(快速参考)
|
|||
|
|
|
|||
|
|
- 新增后端 API 的前端支持:
|
|||
|
|
- `utils/api.ts` 添加调用方法
|
|||
|
|
- `stores/` 添加状态/方法
|
|||
|
|
- `components/` 添加或更新 UI
|
|||
|
|
- `components/nodes/` 若涉及新节点,添加节点组件并更新 `nodeRegistry`
|
|||
|
|
- 新增 UI 视图:在 `pages/` 新建页面,页面中组合已有组件或新增 container 组件。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 十五、附录 - 推荐工具 & VSCode 插件
|
|||
|
|
|
|||
|
|
- ESLint, Prettier, TypeScript, Vitest, Playwright, Tailwind CSS Intellisense
|
|||
|
|
- GitLens, EditorConfig, ESLint plugin for VSCode
|
|||
|
|
|
|||
|
|
---
|