Appearance
可维护性原则 - React应用长期维护指南
本文档详细阐述提升React应用可维护性的原则和实践,确保代码长期健康演进。
1. 代码可读性
1.1 清晰的命名
tsx
// ❌ 不好: 模糊的命名
function Component1({ d, fn }) {
const x = d.map(i => i.n);
return <div onClick={fn}>{x}</div>;
}
// ✅ 好: 清晰的命名
function UserList({ users, onUserSelect }) {
const userNames = users.map(user => user.name);
return <div onClick={onUserSelect}>{userNames}</div>;
}1.2 函数单一职责
tsx
// ❌ 不好: 函数做太多事情
function processUserData(user) {
// 验证
if (!user.email) throw new Error('Invalid email');
// 转换
const normalized = user.email.toLowerCase();
// 存储
localStorage.setItem('user', JSON.stringify(user));
// 发送请求
fetch('/api/users', { method: 'POST', body: JSON.stringify(user) });
// 更新UI
setUser(user);
}
// ✅ 好: 拆分职责
function validateUser(user) {
if (!user.email) throw new Error('Invalid email');
}
function normalizeEmail(email) {
return email.toLowerCase();
}
function saveUserToStorage(user) {
localStorage.setItem('user', JSON.stringify(user));
}
async function createUser(user) {
return fetch('/api/users', {
method: 'POST',
body: JSON.stringify(user)
});
}
// 组合使用
async function handleUserSubmit(user) {
validateUser(user);
const normalized = { ...user, email: normalizeEmail(user.email) };
await createUser(normalized);
saveUserToStorage(normalized);
setUser(normalized);
}1.3 避免魔法数字
tsx
// ❌ 不好: 魔法数字
function Component() {
const [items, setItems] = useState([]);
const loadMore = () => {
fetchItems(items.length, 20); // 20是什么?
};
if (items.length > 100) { // 100是什么?
return <div>Too many items</div>;
}
}
// ✅ 好: 使用常量
const ITEMS_PER_PAGE = 20;
const MAX_ITEMS = 100;
function Component() {
const [items, setItems] = useState([]);
const loadMore = () => {
fetchItems(items.length, ITEMS_PER_PAGE);
};
if (items.length > MAX_ITEMS) {
return <div>Too many items</div>;
}
}2. 代码组织
2.1 目录结构
src/
├── features/ # 按功能模块组织
│ ├── auth/
│ │ ├── components/
│ │ ├── hooks/
│ │ ├── utils/
│ │ └── index.ts
│ ├── dashboard/
│ └── profile/
├── shared/ # 共享代码
│ ├── components/
│ ├── hooks/
│ ├── utils/
│ └── types/
├── services/ # 服务层
│ ├── api/
│ ├── storage/
│ └── analytics/
└── App.tsx2.2 模块化
tsx
// ✅ 功能内聚
// features/auth/index.ts
export { LoginForm } from './components/LoginForm';
export { useAuth } from './hooks/useAuth';
export { authApi } from './api/authApi';
export type { User, AuthState } from './types';
// 使用
import { LoginForm, useAuth } from '@/features/auth';3. 类型安全
3.1 使用TypeScript
tsx
// ✅ 完整的类型定义
interface User {
id: string;
name: string;
email: string;
role: 'admin' | 'user';
}
interface UserListProps {
users: User[];
onUserSelect: (user: User) => void;
loading?: boolean;
}
function UserList({ users, onUserSelect, loading = false }: UserListProps) {
if (loading) return <Spinner />;
return (
<ul>
{users.map(user => (
<li key={user.id} onClick={() => onUserSelect(user)}>
{user.name}
</li>
))}
</ul>
);
}3.2 泛型使用
tsx
// ✅ 使用泛型提高复用性
interface ApiResponse<T> {
data: T;
status: number;
message: string;
}
async function fetchData<T>(url: string): Promise<ApiResponse<T>> {
const response = await fetch(url);
return response.json();
}
// 使用
const userResponse = await fetchData<User[]>('/api/users');
const productResponse = await fetchData<Product[]>('/api/products');4. 文档化
4.1 代码注释
tsx
/**
* 用户列表组件
*
* 展示用户列表,支持搜索、排序和分页
*
* @example
* ```tsx
* <UserList
* users={users}
* onUserSelect={handleSelect}
* searchable
* />
* ```
*/
interface UserListProps {
/** 用户数组 */
users: User[];
/** 用户选择回调 */
onUserSelect: (user: User) => void;
/** 是否显示搜索框 */
searchable?: boolean;
/** 加载状态 */
loading?: boolean;
}
function UserList({
users,
onUserSelect,
searchable = false,
loading = false
}: UserListProps) {
// 实现...
}4.2 README文档
markdown
# UserManagement Feature
## 功能说明
用户管理模块,提供用户列表、详情、编辑等功能。
## 使用方法
```tsx
import { UserList, UserDetail } from '@/features/userManagement';
function App() {
return (
<div>
<UserList onUserSelect={handleSelect} />
<UserDetail userId={selectedUserId} />
</div>
);
}API
Components
UserList: 用户列表组件UserDetail: 用户详情组件UserForm: 用户表单组件
Hooks
useUsers(): 获取用户列表useUser(id): 获取单个用户useUserMutation(): 用户CRUD操作
注意事项
- 需要用户认证权限
- 列表支持虚拟滚动
## 5. 测试
### 5.1 单元测试
```tsx
// UserList.test.tsx
import { render, screen, fireEvent } from '@testing-library/react';
import { UserList } from './UserList';
describe('UserList', () => {
const mockUsers = [
{ id: '1', name: 'John', email: 'john@example.com', role: 'user' },
{ id: '2', name: 'Jane', email: 'jane@example.com', role: 'admin' }
];
it('renders user list', () => {
render(<UserList users={mockUsers} onUserSelect={() => {}} />);
expect(screen.getByText('John')).toBeInTheDocument();
expect(screen.getByText('Jane')).toBeInTheDocument();
});
it('calls onUserSelect when user is clicked', () => {
const handleSelect = jest.fn();
render(<UserList users={mockUsers} onUserSelect={handleSelect} />);
fireEvent.click(screen.getByText('John'));
expect(handleSelect).toHaveBeenCalledWith(mockUsers[0]);
});
it('shows spinner when loading', () => {
render(<UserList users={[]} onUserSelect={() => {}} loading />);
expect(screen.getByTestId('spinner')).toBeInTheDocument();
});
});5.2 集成测试
tsx
// UserFlow.integration.test.tsx
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import { UserManagement } from './UserManagement';
import { server } from '@/mocks/server';
describe('User Management Flow', () => {
beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());
it('complete user management flow', async () => {
render(<UserManagement />);
// 等待用户列表加载
await waitFor(() => {
expect(screen.getByText('John')).toBeInTheDocument();
});
// 点击用户
fireEvent.click(screen.getByText('John'));
// 等待详情加载
await waitFor(() => {
expect(screen.getByText('john@example.com')).toBeInTheDocument();
});
// 点击编辑
fireEvent.click(screen.getByText('Edit'));
// 修改名称
fireEvent.change(screen.getByLabelText('Name'), {
target: { value: 'John Doe' }
});
// 提交
fireEvent.click(screen.getByText('Save'));
// 验证更新
await waitFor(() => {
expect(screen.getByText('John Doe')).toBeInTheDocument();
});
});
});6. 错误处理
6.1 统一错误处理
tsx
// ✅ 创建统一错误处理机制
class AppError extends Error {
constructor(
message: string,
public code: string,
public statusCode: number = 500
) {
super(message);
this.name = 'AppError';
}
}
// 错误处理Hook
function useErrorHandler() {
const showError = (error: Error) => {
if (error instanceof AppError) {
toast.error(`${error.code}: ${error.message}`);
} else {
toast.error('An unexpected error occurred');
console.error(error);
}
};
return { showError };
}
// 使用
function Component() {
const { showError } = useErrorHandler();
const handleSubmit = async (data) => {
try {
await submitData(data);
} catch (error) {
showError(error);
}
};
}6.2 错误边界
tsx
// ✅ 功能级错误边界
function FeatureErrorBoundary({ children, featureName }) {
return (
<ErrorBoundary
fallback={(error, reset) => (
<div>
<h3>{featureName} Error</h3>
<p>{error.message}</p>
<button onClick={reset}>Try Again</button>
</div>
)}
onError={(error, errorInfo) => {
logError({
feature: featureName,
error,
errorInfo
});
}}
>
{children}
</ErrorBoundary>
);
}
// 使用
<FeatureErrorBoundary featureName="User Management">
<UserManagement />
</FeatureErrorBoundary>7. 性能监控
7.1 组件性能追踪
tsx
// ✅ 使用Profiler追踪性能
import { Profiler } from 'react';
function onRenderCallback(
id, phase, actualDuration, baseDuration,
startTime, commitTime, interactions
) {
// 记录慢渲染
if (actualDuration > 16) { // 超过16ms
logPerformance({
component: id,
phase,
duration: actualDuration,
timestamp: commitTime
});
}
}
function App() {
return (
<Profiler id="App" onRender={onRenderCallback}>
<MainContent />
</Profiler>
);
}7.2 自定义监控
tsx
// ✅ 自定义性能监控Hook
function usePerformanceMonitor(componentName: string) {
const renderCount = useRef(0);
const slowRenders = useRef(0);
useEffect(() => {
renderCount.current++;
// 每10次渲染报告一次
if (renderCount.current % 10 === 0) {
reportMetrics({
component: componentName,
totalRenders: renderCount.current,
slowRenders: slowRenders.current
});
}
});
return {
incrementSlowRender: () => slowRenders.current++
};
}8. 代码审查
typescript
const codeReviewChecklist = {
功能: [
'✅ 是否实现了需求',
'✅ 是否有边界情况处理',
'✅ 是否有错误处理',
'✅ 是否有加载状态',
'✅ 是否有空状态处理'
],
代码质量: [
'✅ 命名是否清晰',
'✅ 函数是否单一职责',
'✅ 是否有重复代码',
'✅ 是否有魔法数字',
'✅ 复杂逻辑是否有注释'
],
性能: [
'✅ 是否有不必要的渲染',
'✅ 是否需要useMemo/useCallback',
'✅ 列表是否有key',
'✅ 是否需要代码分割',
'✅ 图片是否优化'
],
测试: [
'✅ 是否有单元测试',
'✅ 测试覆盖率是否足够',
'✅ 是否测试了边界情况',
'✅ 是否有集成测试'
],
安全: [
'✅ 是否有XSS风险',
'✅ 是否有输入验证',
'✅ 敏感数据是否加密',
'✅ API是否有权限检查'
],
可维护性: [
'✅ 代码是否易读',
'✅ 是否有文档',
'✅ 是否使用TypeScript',
'✅ 是否遵循项目规范'
]
};9. 技术债务管理
tsx
// ✅ 标记技术债务
function Component() {
// TODO: [TECH-DEBT] 重构this function
// Issue: #123
// Reason: 逻辑过于复杂
// Estimate: 2 days
const complexFunction = () => {
// 复杂逻辑...
};
// FIXME: [BUG] 修复边界情况
// Issue: #456
// Impact: High
const buggyFunction = () => {
// 有bug的代码...
};
// HACK: [WORKAROUND] 临时方案
// Issue: #789
// Better solution: 等待库更新
const workaroundFunction = () => {
// 临时解决方案...
};
}10. 持续改进
typescript
const continuousImprovement = {
定期重构: [
'识别代码异味',
'简化复杂逻辑',
'消除重复代码',
'改善命名',
'优化性能'
],
技术更新: [
'更新依赖',
'采用新特性',
'改进工具链',
'优化构建',
'提升开发体验'
],
知识分享: [
'代码审查',
'技术分享会',
'文档更新',
'最佳实践总结',
'问题复盘'
],
自动化: [
'自动化测试',
'自动化部署',
'自动化检查',
'自动化监控',
'自动化告警'
]
};11. 总结
提升可维护性的关键:
- 可读性: 清晰命名、单一职责、避免魔法数字
- 组织性: 合理目录结构、模块化
- 类型安全: 使用TypeScript、完整类型定义
- 文档化: 代码注释、README、API文档
- 测试: 单元测试、集成测试、高覆盖率
- 错误处理: 统一机制、错误边界
- 监控: 性能监控、错误追踪
- 代码审查: 使用检查清单、持续改进
- 技术债务: 及时标记、定期清理
- 持续改进: 重构、更新、分享、自动化
可维护性是长期投资,需要团队持续关注和努力。