代码规范
Elftia 使用严格的 ESLint 配置(typescript-eslint/strict),采用渐进式执行策略。
工具链
| 工具 | 用途 | 配置文件 |
|---|---|---|
| ESLint | 代码质量检查(strict 模式) | eslint.config.js |
| Prettier | 代码格式化 | .prettierrc |
| Husky | Git Hooks 管理 | .husky/pre-commit |
| lint-staged | 暂存文件检查 | package.json |
常用命令
# 运行 ESLint 检查
npm run lint:eslint
# 检查 Prettier 格式
npm run lint:prettier
# 自动格式化代码
npm run format
# 完整 lint 检查 (ESLint + TypeScript)
npm run lint
# 检查单个文件
npx eslint path/to/file.ts
# 自动修复单个文件
npx eslint path/to/file.ts --fix
提交前自动检查
提交代码时,Husky 自动运行 lint-staged,对暂存的 .ts/.tsx 文件执行 eslint --fix:
- error 级别:阻止提交,必须修复
- warn 级别:显示警告,允许提交
- auto-fix:自动修复 import 排序等可修复问题
ESLint 规则速查
严重等级
| 等级 | 含义 | 提交时行为 |
|---|---|---|
error | 必须修复 | 阻止提交 |
warn | 建议修复 | 允许提交 |
off | 已禁用 | - |
Error 级别规则(阻止提交)
{/* simple-import-sort/imports — 可自动修复 */}
{/* Import 必须按规定顺序排列,运行 eslint --fix 自动修复 */}
{/* prefer-const */}
let value = 1; // 从未重新赋值,应使用 const
{/* no-var */}
var x = 1; // 禁止使用 var
{/* eqeqeq */}
if (a == b) { } // 使用 == 而非 ===(null 检查除外)
{/* react-hooks/rules-of-hooks */}
if (condition) { useState(); } // Hook 只能在顶层调用
Warn 级别规则(允许提交)
TypeScript 规则
| 规则 | 说明 | 修复方式 |
|---|---|---|
@typescript-eslint/no-unused-vars | 未使用的变量 | 删除,或用 _ 前缀标记 |
@typescript-eslint/no-explicit-any | 使用 any 类型 | 改为具体类型或 unknown |
@typescript-eslint/consistent-type-imports | 非 type-only 导入 | 改为 import { type Foo } |
@typescript-eslint/no-non-null-assertion | 非空断言 ! | 改为可选链 ?. 或类型守卫 |
React 规则
| 规则 | 说明 | 修复方式 |
|---|---|---|
react-hooks/exhaustive-deps | Hook 依赖不完整 | 补全依赖数组 |
react/jsx-no-leaked-render | 渲染泄漏(count && <X />) | 改为 count > 0 && <X /> |
react/self-closing-comp | 空标签未自闭合 | <Comp></Comp> → <Comp /> |
react/jsx-curly-brace-presence | 不必要的大括号 | prop={"val"} → prop="val" |
react/jsx-boolean-value | 多余的布尔值 | disabled={true} → disabled |
react/hook-use-state | 状态名非小驼峰 | [Value, setV] → [value, setV] |
其他规则
| 规则 | 说明 | 修复方式 |
|---|---|---|
no-console | 渲染进程使用 console.log | 改为 console.warn/error/info/debug |
jsx-a11y/* | 无障碍问题 | 参见 设计系统 |
Import 排序规则
Import 必须按以下顺序排列(eslint --fix 可自动修复):
{/* 1. Node.js 内置模块 */}
import path from 'node:path';
import fs from 'node:fs';
{/* 2. 外部包 */}
import React, { useState } from 'react';
import { useQuery } from '@tanstack/react-query';
{/* 3. 内部别名 @/ */}
import { Button } from '@/components/ui/button';
import { useAppState } from '@/shared/hooks/useAppState';
{/* 4. @shared/ 别名 */}
import type { Message } from '@shared/contracts';
{/* 5. @main/ 别名 */}
import { AppPaths } from '@main/services/infra/paths/paths';
{/* 6. 父目录导入 */}
import { utils } from '../utils';
{/* 7. 同级目录导入 */}
import { helper } from './helper';
{/* 8. 样式导入 */}
import './styles.css';
Prettier 配置
| 选项 | 值 | 说明 |
|---|---|---|
semi | true | 使用分号 |
singleQuote | true | 使用单引号 |
tabWidth | 2 | 缩进宽度 |
trailingComma | 'es5' | 尾逗号 |
printWidth | 100 | 行宽限制 |
文件大小限制
黄金法则:单个文件不应超过 600 行代码。
| 文件类型 | 推荐上限 | 警戒线 | 禁止超过 |
|---|---|---|---|
| React 组件 | 400 行 | 600 行 | 800 行 |
| 自定义 Hook | 300 行 | 400 行 | 600 行 |
| 工具/辅助函数 | 150 行 | 200 行 | 300 行 |
| 类型定义 | 100 行 | 150 行 | 200 行 |
| 服务/API | 300 行 | 400 行 | 600 行 |
违反后果:
- 超过警戒线 → 必须在当前任务中拆分,不得留到以后
- 超过禁止线 → 立即停止编写,先重构再继续
命名规范
文件命名
| 类型 | 规则 | 示例 |
|---|---|---|
| React 组件 | PascalCase.tsx | UserMessage.tsx |
| Hooks | use + PascalCase.ts | useChatActions.ts |
| 工具函数 | camelCase.ts | messageHelpers.ts |
| 类型定义 | camelCase.ts 或 types.ts | chatTypes.ts |
| 常量 | UPPER_CASE.ts | API_CONSTANTS.ts |
| 服务 | PascalCase + Service.ts | ChatService.ts |
| 目录 | kebab-case | chat-messages/ |
组件命名
{/* 好:清晰、描述性强 */}
export function UserMessage() { }
export function EditTool() { }
export function ChatComposer() { }
{/* 不好:太泛化 */}
export function Message() { }
export function Tool() { }
export function Input() { }
Hook 命名
{/* 好:use + 功能描述 */}
export function useChatActions() { }
export function useMessageEffects() { }
{/* 不好:不符合 React Hook 命名规范 */}
export function chatActions() { }
export function messageHook() { }
目录结构规范
组件目录
components/
├── ComponentName/
│ ├── index.ts # 统一导出
│ ├── ComponentName.tsx # 主组件
│ ├── types.ts # 类型定义
│ ├── utils.ts # 工具函数
│ ├── SubComponent.tsx # 子组件
│ └── __tests__/
│ └── ComponentName.test.tsx
Hooks 目录
hooks/
├── domain/
│ ├── index.ts # 统一导出
│ ├── useDomainState.ts # 状态
│ ├── useDomainActions.ts # 动作
│ └── useDomainEffects.ts # 副作用
服务目录
services/
├── ServiceName/
│ ├── index.ts # 统一导出
│ ├── ServiceName.ts # 主服务
│ ├── types.ts # 类型定义
│ └── subdomain/ # 子领域
TypeScript 最佳实践
类型导入
{/* 使用 type-only 导入 */}
import { type SomeType } from './types';
import type { AnotherType } from '@shared/contracts';
避免 any
{/* 不好 */}
function handle(data: any) { }
{/* 好:使用具体类型 */}
function handle(data: Message) { }
{/* 好:使用泛型 */}
function handle<T extends BaseMessage>(data: T) { }
{/* 好:使用 unknown + 类型守卫 */}
function handle(data: unknown) {
if (isMessage(data)) { /* data: Message */ }
}
Barrel Export
{/* components/chat/messages/index.ts */}
export { MessageItem } from './MessageItem';
export { MessageList } from './MessageList';
export type { MessageItemProps } from './MessageItem';
代码组织顺序
React 组件结构
{/* 1. 导入 */}
{/* 2. 类型定义 */}
{/* 3. 常量 */}
{/* 4. 辅助函数 */}
{/* 5. 主组件 */}
{/* 5.1 Hooks */}
{/* 5.2 派生状态 (useMemo) */}
{/* 5.3 事件处理器 (useCallback) */}
{/* 5.4 Effects (useEffect) */}
{/* 5.5 渲染 (return) */}
{/* 6. 子组件(如果简单) */}
单一职责原则
每个文件只有一个改变的理由。当组件出现以下情况时应拆分:
- 超过 300 行代码
- 包含 3 个以上独立逻辑块
- 有可复用的部分
- 不同部分的变更频率不同
- 测试变得困难
拆分策略:
| 策略 | 适用场景 | 示例 |
|---|---|---|
| 按功能拆分 | UI 区块独立 | 工具栏 / 消息列表 / 输入框 → 独立组件 |
| 按数据流拆分 | 状态逻辑复杂 | 状态 / 动作 / 副作用 → 独立 Hook |
| 按领域拆分 | 多种同类项 | EditTool / WriteTool / BashTool → 独立文件 |
Git 工作流
提交规范
使用 Conventional Commits 格式:
feat: 新功能描述
fix: 修复描述
refactor: 重构描述
docs: 文档更新
style: 代码格式调整(不影响逻辑)
test: 测试相关
chore: 构建工具或辅助工具变动
提交前检查清单
- 运行
npm run lint— 确保无 error - 修复所有 error 级别问题
- 尽量修复 warn 级别问题
- 运行
npm run format— 格式化代码 - 新文件应该 0 warning
- 修改现有文件时,顺便修复该文件的 warning