🎯 为什么需要编码规范?
问题现状
- ❌ 没有规范 → 代码像"杂乱的拼图",难以拼接和维护
- ❌ 风格混乱 → 团队协作像"多国语言会议",沟通成本极高
- ❌ 类型滥用 → TypeScript 优势无法发挥,像"有地图却迷路"
- ❌ 性能问题 → 不必要的重渲染和内存泄漏,像"漏水的容器"
规范价值
- ✅ 统一规范 → 团队协作如"精心编排的交响乐",和谐有序
- ✅ 可维护性 → 代码修改像"模块化积木",轻松重构
- ✅ 可读性 → 新成员理解代码像"阅读优美的散文",流畅自然
- ✅ 质量保障 → 减少潜在 bug,构建"坚实的代码地基"
业界领袖观点
- "好的代码,应该像诗一样优雅。" --- 尤雨溪 (Vue.js 创作者)
- "规范不是限制创造力的牢笼,而是提升工程效率的翅膀。" --- Dan Abramov (Redux 作者)
- "在软件工程中,一致性比个人风格更重要。" --- Martin Fowler (重构之父)
🗂️ 文件组织规范
1.1 单一模块原则
typescript
// ✅ Good: 一个文件一个主要组件
// UserProfile.tsx
function UserProfile() {
return <div>User Profile</div>;
}
// 可以在同一文件定义辅助组件
function UserAvatar() {
return <img src="avatar.jpg" alt="User Avatar" />;
}
function UserStats() {
return <div>User Statistics</div>;
}
export default UserProfile;
// ❌ Bad: 多个主要组件混合
function UserProfile() { ... }
function ProductCard() { ... } // 不应该在同一文件
export default UserProfile;
理由:提高可读性、便于单元测试和代码复用。
1.2 目录结构规范
src/
├── components/ # 组件目录
│ ├── common/ # 通用组件 (Button, Input, Modal)
│ ├── business/ # 业务组件 (UserCard, ProductList)
│ ├── layouts/ # 布局组件 (Header, Sidebar, Footer)
│ └── forms/ # 表单组件
├── hooks/ # 自定义 Hooks
├── types/ # 类型定义
├── utils/ # 工具函数
├── services/ # API 服务
├── constants/ # 常量定义
├── styles/ # 样式文件
└── assets/ # 静态资源
1.3 模块导入规范
typescript
// ✅ Good: 清晰的导入顺序和分组
// 1. 外部依赖 (React, 第三方库)
import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { Button, Input, message } from 'antd';
// 2. 类型导入 (使用 import type 优化打包)
import type { User, ApiResponse, PaginationParams } from '@/types';
// 3. 工具函数和配置
import { formatDate, debounce, classNames } from '@/utils/helpers';
import { API_ENDPOINTS } from '@/constants/api';
// 4. 服务和 API
import { userApi, authApi } from '@/services/api';
// 5. 组件
import UserAvatar from './UserAvatar';
import LoadingSpinner from '../common/LoadingSpinner';
// 6. 样式 (CSS Modules 优先)
import styles from './UserProfile.module.scss';
// ❌ Bad: 混乱的导入顺序
import { formatDate } from '@/utils/helpers';
import React from 'react';
import UserAvatar from './UserAvatar';
import { Button } from 'antd';
⚛️ 组件定义规范
2.1 函数式组件标准写法
typescript
// ✅ Good: 完整的类型定义和默认值
interface UserProfileProps {
userId: number;
userName: string;
isActive?: boolean;
onUpdate?: (user: User) => void;
onDelete?: (userId: number) => void;
className?: string;
}
const UserProfile: React.FC<UserProfileProps> = ({
userId,
userName,
isActive = false,
onUpdate,
onDelete,
className = ''
}) => {
const [user, setUser] = useState<User | null>(null);
const [loading, setLoading] = useState(false);
// 使用 useCallback 优化性能
const handleUpdate = useCallback((updatedUser: User) => {
setUser(updatedUser);
onUpdate?.(updatedUser);
}, [onUpdate]);
return (
<div className={`${styles.container} ${className}`}>
<h2>{userName}</h2>
<UserAvatar userId={userId} />
<UserActions
isActive={isActive}
onUpdate={handleUpdate}
onDelete={onDelete}
/>
</div>
);
};
export default UserProfile;
// ❌ Bad: 箭头函数组件(调试困难)
const UserProfile = ({ userId, userName }) => (
<div>
<h2>{userName}</h2>
</div>
);
2.2 类组件规范(遗留代码维护)
typescript
// ✅ Good: 明确的类型定义和生命周期管理
interface UserListState {
users: User[];
loading: boolean;
error: string | null;
pagination: Pagination;
}
interface UserListProps {
initialPage?: number;
pageSize?: number;
onUserSelect?: (user: User) => void;
}
class UserList extends React.Component<UserListProps, UserListState> {
// 默认属性值
static defaultProps: Partial<UserListProps> = {
initialPage: 1,
pageSize: 20
};
constructor(props: UserListProps) {
super(props);
this.state = {
users: [],
loading: false,
error: null,
pagination: {
page: props.initialPage!,
pageSize: props.pageSize!,
total: 0
}
};
}
componentDidMount() {
this.fetchUsers();
}
componentDidUpdate(prevProps: UserListProps, prevState: UserListState) {
// 当分页变化时重新获取数据
if (prevState.pagination.page !== this.state.pagination.page) {
this.fetchUsers();
}
}
componentWillUnmount() {
// 清理操作
this.abortController?.abort();
}
private abortController: AbortController | null = null;
private fetchUsers = async () => {
this.setState({ loading: true, error: null });
// 取消之前的请求
this.abortController?.abort();
this.abortController = new AbortController();
try {
const { page, pageSize } = this.state.pagination;
const response = await userApi.getUsers({ page, pageSize }, {
signal: this.abortController.signal
});
this.setState({
users: response.data,
pagination: { ...this.state.pagination, total: response.total }
});
} catch (error) {
if (error.name !== 'AbortError') {
this.setState({ error: 'Failed to fetch users' });
}
} finally {
this.setState({ loading: false });
}
};
private handlePageChange = (page: number) => {
this.setState(prevState => ({
pagination: { ...prevState.pagination, page }
}));
};
render() {
const { users, loading, error, pagination } = this.state;
const { onUserSelect } = this.props;
if (loading) return <LoadingSpinner />;
if (error) return <ErrorMessage message={error} />;
return (
<div className={styles.userList}>
{users.map(user => (
<UserCard
key={user.id}
user={user}
onSelect={onUserSelect}
/>
))}
<Pagination
current={pagination.page}
pageSize={pagination.pageSize}
total={pagination.total}
onChange={this.handlePageChange}
/>
</div>
);
}
}
2.3 高阶组件(HOC)规范
typescript
// ✅ Good: 类型安全的 HOC 实现
function withAuth<P extends object>(
WrappedComponent: React.ComponentType<P>,
requiredRole?: UserRole
) {
// 返回组件的 Props 类型,排除被 HOC 处理的属性
type Props = Omit<P, 'user' | 'isAuthenticated'>;
const WithAuth: React.FC<Props> = (props) => {
const { user, isAuthenticated } = useAuth();
// 权限检查
if (!isAuthenticated) {
return <Redirect to="/login" />;
}
if (requiredRole && user?.role !== requiredRole) {
return <div>Access Denied</div>;
}
// 类型断言确保属性传递安全
return <WrappedComponent {...props as P} user={user} />;
};
// 设置 displayName 便于调试
const wrappedComponentName = WrappedComponent.displayName
|| WrappedComponent.name
|| 'Component';
WithAuth.displayName = `withAuth(${wrappedComponentName})`;
return WithAuth;
}
// 使用示例
interface DashboardProps {
user: User;
dashboardData: DashboardData;
}
const Dashboard: React.FC<DashboardProps> = ({ user, dashboardData }) => {
return (
<div>
<h1>Welcome, {user.name}</h1>
{/* 仪表板内容 */}
</div>
);
};
// 应用 HOC
const ProtectedDashboard = withAuth(Dashboard, 'admin');
2.4 自定义 Hook 规范
typescript
// ✅ Good: 完整的自定义 Hook 实现
interface UseUserOptions {
autoFetch?: boolean;
onSuccess?: (user: User) => void;
onError?: (error: string) => void;
}
interface UseUserReturn {
user: User | null;
loading: boolean;
error: string | null;
updateUser: (updates: Partial<User>) => Promise<void>;
refetch: () => Promise<void>;
reset: () => void;
}
/**
* 用户数据管理 Hook
*
* @param userId - 用户 ID
* @param options - 配置选项
* @returns 用户状态和管理方法
*
* @example
* const { user, loading, updateUser } = useUser(123, {
* onSuccess: (user) => console.log('User loaded:', user)
* });
*/
function useUser(userId: number, options: UseUserOptions = {}): UseUserReturn {
const {
autoFetch = true,
onSuccess,
onError
} = options;
const [user, setUser] = useState<User | null>(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const fetchUser = useCallback(async () => {
if (!userId) return;
try {
setLoading(true);
setError(null);
const userData = await userApi.getUser(userId);
setUser(userData);
onSuccess?.(userData);
} catch (err) {
const errorMessage = err instanceof Error ? err.message : 'Failed to fetch user';
setError(errorMessage);
onError?.(errorMessage);
} finally {
setLoading(false);
}
}, [userId, onSuccess, onError]);
const updateUser = useCallback(async (updates: Partial<User>) => {
if (!user) return;
try {
setLoading(true);
const updatedUser = await userApi.updateUser(user.id, updates);
setUser(updatedUser);
onSuccess?.(updatedUser);
} catch (err) {
const errorMessage = err instanceof Error ? err.message : 'Failed to update user';
setError(errorMessage);
onError?.(errorMessage);
throw err;
} finally {
setLoading(false);
}
}, [user, onSuccess, onError]);
const reset = useCallback(() => {
setUser(null);
setError(null);
setLoading(false);
}, []);
// 自动获取用户数据
useEffect(() => {
if (autoFetch && userId) {
fetchUser();
}
}, [autoFetch, userId, fetchUser]);
return {
user,
loading,
error,
updateUser,
refetch: fetchUser,
reset
};
}
🔤 命名规范
3.1 文件命名规范
typescript
// ✅ Good: PascalCase 组件文件
UserProfile.tsx
NavigationMenu.tsx
DatePicker.tsx
ErrorBoundary.tsx
// ✅ Good: camelCase 工具文件和 Hook 文件
formatDate.ts
apiClient.ts
useLocalStorage.ts
validationUtils.ts
// ✅ Good: kebab-case 样式文件和资源
user-profile.module.scss
main-header.component.tsx
icon-arrow-right.svg
// ❌ Bad
userProfile.tsx // 组件应该用 PascalCase
user-profile.tsx // 应该用 PascalCase
User_Profile.tsx // 不要用下划线
3.2 组件命名规范
typescript
// ✅ Good: PascalCase 组件名
import UserProfile from './UserProfile';
import NavigationMenu from './NavigationMenu';
import DatePicker from './DatePicker';
// 使用组件
const userProfile = <UserProfile />;
const navigation = <NavigationMenu items={menuItems} />;
// ❌ Bad
import user_profile from './user_profile';
import navigationMenu from './NavigationMenu';
const UserProfile = <user_profile />; // 组件实例应该 camelCase
const Navigation = <navigationMenu />; // 组件名应该 PascalCase
3.3 属性命名规范
typescript
// ✅ Good: 语义化属性名
<UserCard
userData={user} // 明确的数据类型
isActive={true} // 布尔值前缀 is/has/can
isLoading={false} // 加载状态
hasError={false} // 错误状态
canEdit={true} // 权限控制
variant="primary" // 样式变体
size="large" // 尺寸
onUserUpdate={handleUpdate} // 事件处理器前缀 on
onDeleteSuccess={handleSuccess} // 明确的事件结果
onSubmit={handleSubmit} // 表单提交
/>
// ❌ Bad: 使用 DOM 关键词或模糊命名
<UserCard
style="fancy" // 应该用 variant
className="active" // 应该用 isActive
click={handleClick} // 应该用 onClick
change={handleChange} // 应该用 onChange
data={user} // 过于模糊
/>
3.4 事件处理函数命名
typescript
// ✅ Good: 清晰的命名约定
// 基础事件处理
const handleClick = (event: React.MouseEvent) => { ... };
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => { ... };
const handleSubmit = (event: React.FormEvent) => { ... };
// 带具体上下文的事件
const handleUserClick = (userId: number, event: React.MouseEvent) => { ... };
const handleInputChange = (value: string, fieldName: string) => { ... };
const handleFormSubmit = (formData: FormData) => { ... };
// 异步操作事件
const handleUserDelete = async (userId: number) => {
try {
await deleteUser(userId);
onDeleteSuccess?.(userId);
} catch (error) {
setError('Delete failed');
}
};
// ❌ Bad: 模糊的命名
const submit = () => { ... };
const click = (id: number) => { ... };
const change = (val: string) => { ... };
3.5 变量和函数命名
typescript
// ✅ Good: 描述性的命名
const userList = users.filter(user => user.isActive);
const isLoading = fetchStatus === 'loading';
const hasPermission = user.role === 'admin';
const formattedDate = formatDate(timestamp, 'YYYY-MM-DD');
// 函数命名
const calculateTotalPrice = (items: CartItem[]) => { ... };
const validateEmailFormat = (email: string) => { ... };
const generateReportData = (filters: ReportFilters) => { ... };
// ❌ Bad: 模糊的命名
const list = users.filter(u => u.active); // 什么列表?
const loading = status === 'loading'; // 什么在加载?
const check = (email: string) => { ... }; // 检查什么?
📝 JSX 书写规范
4.1 对齐与格式规范
typescript
// ✅ Good: 多行属性对齐
<Modal
isOpen={isModalOpen}
onClose={handleClose}
title="用户设置"
size="large"
backdrop={true}
closeOnOverlayClick={false}
showCloseButton={true}
>
<UserForm
initialData={userData}
onSubmit={handleSubmit}
validationRules={validationRules}
submitText="保存更改"
cancelText="取消"
/>
</Modal>
// ✅ Good: 单行组件简洁
<Button variant="primary" onClick={handleClick}>
提交
</Button>
<Avatar src={user.avatar} alt={user.name} size={40} />
// ✅ Good: 有子元素的正常缩进
<Card>
<Card.Header>
<h3>用户信息</h3>
</Card.Header>
<Card.Body>
<p>姓名: {user.name}</p>
<p>邮箱: {user.email}</p>
</Card.Body>
<Card.Footer>
<Button onClick={handleEdit}>编辑</Button>
</Card.Footer>
</Card>
// ❌ Bad: 混乱的对齐
<Modal isOpen={isModalOpen} onClose={handleClose}
title="用户设置" size="large">
<UserForm initialData={userData} onSubmit={handleSubmit}
validationRules={validationRules} />
</Modal>
4.2 引号规范
typescript
// ✅ Good: JSX 属性双引号,JS/TS 单引号
<Button
variant="primary"
onClick={handleClick}
disabled={isLoading}
>
点击我
</Button>
// JavaScript/Typescript 使用单引号
const message = 'Hello World';
const className = 'btn-primary';
const style = { color: 'red', fontSize: '14px' };
// ❌ Bad
<Button variant='primary' onClick={handleClick}>
点击我
</Button>
const message = "Hello World";
const style = { color: "red" };
4.3 自闭合标签规范
typescript
// ✅ Good: 自闭合标签前加空格
<Input />
<Icon type="user" />
<Image src="avatar.jpg" alt="用户头像" />
<Br />
<Hr />
// ❌ Bad: 缺少空格
<Input/>
<Icon type="user"/>
<Image src="avatar.jpg" alt="用户头像"/>
4.4 布尔值属性规范
typescript
// ✅ Good: 布尔值属性可省略值
<Modal visible /> // 等同于 visible={true}
<Button disabled /> // 等同于 disabled={true}
<Input required /> // 等同于 required={true}
<Checkbox checked /> // 等同于 checked={true}
// 明确的 false 值需要显式写出
<Modal visible={false} />
<Button disabled={false} />
// ❌ Bad: 多余的 true 值
<Modal visible={true} />
<Button disabled={true} />
<Input required={true} />
4.5 无障碍访问规范
typescript
// ✅ Good: 完整的无障碍支持
// 图片必须有 alt 属性
<img
src="user-avatar.jpg"
alt="用户头像 - 张三"
width={64}
height={64}
loading="lazy"
/>
// 按钮必须有明确的标签
<button
onClick={handleDelete}
aria-label="删除用户"
aria-describedby="delete-help-text"
disabled={isDeleting}
>
{isDeleting ? '删除中...' : '删除'}
</button>
<div id="delete-help-text">永久删除用户,此操作不可撤销</div>
// 表单元素必须有标签关联
<label htmlFor="username-input">用户名</label>
<input
id="username-input"
type="text"
aria-required="true"
aria-invalid={!!errors.username}
aria-describedby="username-error"
/>
// 装饰性图片
<img
src="background-pattern.png"
alt=""
role="presentation"
/>
// ❌ Bad: 缺少无障碍支持
<img src="avatar.jpg" />
<button onClick={handleClick}>🗑️</button>
<input type="text" />
🏷️ TypeScript 类型规范
5.1 接口与类型定义规范
typescript
// ✅ Good: 清晰的接口定义
interface User {
// 必需属性
id: number;
name: string;
email: string;
// 可选属性
avatar?: string;
phone?: string;
// 枚举-like 属性
role: UserRole;
status: 'active' | 'inactive' | 'suspended';
// 日期类型
createdAt: Date;
updatedAt: Date;
// 嵌套对象
profile: {
bio: string;
website?: string;
location: string;
};
// 数组类型
tags: string[];
}
// ✅ Good: 使用 type 定义联合类型和工具类型
type UserRole = 'admin' | 'user' | 'guest' | 'moderator';
type ApiResponse<T = unknown> = {
data: T;
success: boolean;
message?: string;
timestamp: Date;
};
type PaginationParams = {
page: number;
pageSize: number;
sortBy?: string;
sortOrder?: 'asc' | 'desc';
};
// 工具类型
type PartialUser = Partial<User>;
type UserPreview = Pick<User, 'id' | 'name' | 'avatar' | 'role'>;
type UserWithoutId = Omit<User, 'id'>;
// ❌ Bad: 使用 any 或过于宽泛的类型
interface BadUser {
id: any; // 应该明确类型
data: object; // 过于宽泛
metadata: any; // 应该定义具体结构
}
5.2 组件 Props 类型规范
typescript
// ✅ Good: 完整的 Props 类型定义
interface SearchInputProps {
// 必需属性
value: string;
onChange: (value: string) => void;
// 可选属性
placeholder?: string;
disabled?: boolean;
autoFocus?: boolean;
maxLength?: number;
// 样式相关
className?: string;
style?: React.CSSProperties;
size?: 'small' | 'medium' | 'large';
variant?: 'outline' | 'filled' | 'flushed';
// 事件处理
onSearch?: (value: string) => void;
onFocus?: (event: React.FocusEvent<HTMLInputElement>) => void;
onBlur?: (event: React.FocusEvent<HTMLInputElement>) => void;
onKeyDown?: (event: React.KeyboardEvent<HTMLInputElement>) => void;
// 功能扩展
showClearButton?: boolean;
loading?: boolean;
icon?: React.ReactNode;
}
const SearchInput: React.FC<SearchInputProps> = ({
value,
onChange,
placeholder = '搜索...',
disabled = false,
autoFocus = false,
maxLength,
className = '',
style,
size = 'medium',
variant = 'outline',
onSearch,
onFocus,
onBlur,
onKeyDown,
showClearButton = true,
loading = false,
icon
}) => {
const handleKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
if (event.key === 'Enter') {
onSearch?.(value);
}
onKeyDown?.(event);
};
const handleClear = () => {
onChange('');
onSearch?.('');
};
return (
<div className={`${styles.container} ${styles[size]} ${styles[variant]} ${className}`}>
{icon && <span className={styles.icon}>{icon}</span>}
<input
type="text"
value={value}
onChange={(e) => onChange(e.target.value)}
placeholder={placeholder}
disabled={disabled}
autoFocus={autoFocus}
maxLength={maxLength}
onFocus={onFocus}
onBlur={onBlur}
onKeyDown={handleKeyDown}
className={styles.input}
style={style}
/>
{showClearButton && value && (
<button
onClick={handleClear}
className={styles.clearButton}
aria-label="清除搜索"
>
×
</button>
)}
{loading && <div className={styles.spinner} />}
</div>
);
};
5.3 事件类型定义规范
typescript
// ✅ Good: 明确的事件类型定义
interface FormEvents {
// 表单事件
onSubmit: (event: React.FormEvent<HTMLFormElement>) => void;
onReset: (event: React.FormEvent<HTMLFormElement>) => void;
// 输入事件
onChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
onInput: (event: React.FormEvent<HTMLInputElement>) => void;
// 焦点事件
onFocus: (event: React.FocusEvent<HTMLInputElement>) => void;
onBlur: (event: React.FocusEvent<HTMLInputElement>) => void;
// 鼠标事件
onClick: (event: React.MouseEvent<HTMLButtonElement>) => void;
onDoubleClick: (event: React.MouseEvent<HTMLDivElement>) => void;
onMouseEnter: (event: React.MouseEvent<HTMLElement>) => void;
onMouseLeave: (event: React.MouseEvent<HTMLElement>) => void;
// 键盘事件
onKeyDown: (event: React.KeyboardEvent<HTMLInputElement>) => void;
onKeyUp: (event: React.KeyboardEvent<HTMLInputElement>) => void;
onKeyPress: (event: React.KeyboardEvent<HTMLInputElement>) => void;
}
// 自定义事件类型
interface CustomEvents {
onUserSelect: (user: User, event: React.MouseEvent) => void;
onDataLoad: (data: unknown[], error?: Error) => void;
onStateChange: <T>(key: string, value: T, previousValue: T) => void;
}
// 使用示例
const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
const formData = new FormData(event.currentTarget);
// 处理提交逻辑
};
const handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const { name, value, type, checked } = event.target;
setFormData(prev => ({
...prev,
[name]: type === 'checkbox' ? checked : value
}));
};
const handleUserClick = (user: User, event: React.MouseEvent) => {
event.stopPropagation();
onUserSelect?.(user);
};
5.4 泛型组件规范
typescript
// ✅ Good: 类型安全的泛型组件
interface ListProps<T> {
// 数据相关
items: T[];
loading?: boolean;
emptyMessage?: string;
// 渲染相关
renderItem: (item: T, index: number) => React.ReactNode;
keyExtractor: (item: T) => string | number;
// 布局相关
direction?: 'vertical' | 'horizontal';
gap?: number;
className?: string;
// 功能相关
onItemClick?: (item: T, index: number) => void;
loadMore?: () => void;
hasMore?: boolean;
}
/**
* 通用列表组件
*
* @template T - 列表项数据类型
*/
function GenericList<T>({
items,
loading = false,
emptyMessage = "暂无数据",
renderItem,
keyExtractor,
direction = 'vertical',
gap = 8,
className = '',
onItemClick,
loadMore,
hasMore = false
}: ListProps<T>) {
if (loading && items.length === 0) {
return <LoadingSpinner />;
}
if (items.length === 0) {
return (
<div className={styles.emptyState}>
{emptyMessage}
</div>
);
}
const containerStyle = {
flexDirection: direction === 'horizontal' ? 'row' : 'column',
gap: `${gap}px`
} as React.CSSProperties;
return (
<div className={`${styles.container} ${className}`}>
<div className={styles.list} style={containerStyle}>
{items.map((item, index) => (
<div
key={keyExtractor(item)}
className={onItemClick ? styles.clickableItem : styles.item}
onClick={onItemClick ? () => onItemClick(item, index) : undefined}
>
{renderItem(item, index)}
</div>
))}
</div>
{hasMore && (
<div className={styles.loadMore}>
<Button onClick={loadMore} variant="outline">
加载更多
</Button>
</div>
)}
</div>
);
}
// 使用示例
interface User {
id: number;
name: string;
email: string;
}
// 用户列表
<GenericList
items={users}
keyExtractor={(user) => user.id}
renderItem={(user) => (
<UserCard user={user} />
)}
onItemClick={(user) => selectUser(user)}
/>
// 产品列表
<GenericList
items={products}
keyExtractor={(product) => product.sku}
renderItem={(product) => (
<ProductItem product={product} />
)}
direction="horizontal"
gap={16}
/>
🔄 状态管理规范
6.1 useState 规范
typescript
// ✅ Good: 明确的状态类型和初始值
// 基础类型状态
const [count, setCount] = useState<number>(0);
const [name, setName] = useState<string>('');
const [isLoading, setIsLoading] = useState<boolean>(false);
const [error, setError] = useState<string | null>(null);
// 对象类型状态
interface UserFormState {
name: string;
email: string;
age: number;
interests: string[];
}
const [formData, setFormData] = useState<UserFormState>({
name: '',
email: '',
age: 0,
interests: []
});
// 数组类型状态
const [users, setUsers] = useState<User[]>([]);
const [selectedIds, setSelectedIds] = useState<Set<number>>(new Set());
// 使用函数初始化复杂状态
const [config, setConfig] = useState<AppConfig>(() => {
const saved = localStorage.getItem('app-config');
return saved ? JSON.parse(saved) : DEFAULT_CONFIG;
});
// ❌ Bad: 隐式的 any 类型
const [user, setUser] = useState(null); // 应该明确类型
const [data, setData] = useState([]); // 应该明确数组类型
const [filters, setFilters] = useState({}); // 应该定义接口
6.2 useEffect 规范
typescript
// ✅ Good: 清晰的副作用管理和依赖处理
useEffect(() => {
let mounted = true;
let abortController: AbortController | null = null;
const fetchData = async () => {
if (!userId) return;
try {
setIsLoading(true);
setError(null);
// 创建 AbortController 用于取消请求
abortController = new AbortController();
const userData = await userApi.getUser(userId, {
signal: abortController.signal
});
// 检查组件是否仍挂载
if (mounted) {
setUser(userData);
onUserLoad?.(userData);
}
} catch (err) {
if (mounted && err.name !== 'AbortError') {
setError(err instanceof Error ? err.message : 'Failed to fetch user');
}
} finally {
if (mounted) {
setIsLoading(false);
}
}
};
fetchData();
// 清理函数
return () => {
mounted = false;
abortController?.abort();
};
}, [userId, onUserLoad]); // 明确的依赖数组
// ✅ Good: 事件监听和定时器管理
useEffect(() => {
const handleResize = () => {
setWindowSize({
width: window.innerWidth,
height: window.innerHeight
});
};
const handleKeyPress = (event: KeyboardEvent) => {
if (event.key === 'Escape') {
onClose?.();
}
};
window.addEventListener('resize', handleResize);
document.addEventListener('keydown', handleKeyPress);
return () => {
window.removeEventListener('resize', handleResize);
document.removeEventListener('keydown', handleKeyPress);
};
}, [onClose]);
// ✅ Good: 条件执行的副作用
useEffect(() => {
if (!isOpen) return;
// 只在模态框打开时执行的逻辑
const timer = setTimeout(() => {
setAutoFocus(true);
}, 100);
return () => clearTimeout(timer);
}, [isOpen]);
6.3 useReducer 规范
typescript
// ✅ Good: 复杂状态管理使用 useReducer
interface TodoState {
todos: Todo[];
filter: 'all' | 'active' | 'completed';
loading: boolean;
error: string | null;
}
type TodoAction =
| { type: 'SET_LOADING'; payload: boolean }
| { type: 'SET_ERROR'; payload: string | null }
| { type: 'ADD_TODO'; payload: Todo }
| { type: 'UPDATE_TODO'; payload: { id: number; updates: Partial<Todo> } }
| { type: 'DELETE_TODO'; payload: number }
| { type: 'SET_FILTER'; payload: TodoState['filter'] }
| { type: 'CLEAR_COMPLETED' };
const todoReducer = (state: TodoState, action: TodoAction): TodoState => {
switch (action.type) {
case 'SET_LOADING':
return { ...state, loading: action.payload };
case 'SET_ERROR':
return { ...state, error: action.payload };
case 'ADD_TODO':
return {
...state,
todos: [...state.todos, action.payload]
};
case 'UPDATE_TODO':
return {
...state,
todos: state.todos.map(todo =>
todo.id === action.payload.id
? { ...todo, ...action.payload.updates }
: todo
)
};
case 'DELETE_TODO':
return {
...state,
todos: state.todos.filter(todo => todo.id !== action.payload)
};
case 'SET_FILTER':
return { ...state, filter: action.payload };
case 'CLEAR_COMPLETED':
return {
...state,
todos: state.todos.filter(todo => !todo.completed)
};
default:
return state;
}
};
// 使用 useReducer
const [state, dispatch] = useReducer(todoReducer, {
todos: [],
filter: 'all',
loading: false,
error: null
});
// Action creators
const addTodo = (text: string) => {
const newTodo: Todo = {
id: Date.now(),
text,
completed: false,
createdAt: new Date()
};
dispatch({ type: 'ADD_TODO', payload: newTodo });
};
const toggleTodo = (id: number) => {
dispatch({
type: 'UPDATE_TODO',
payload: { id, updates: { completed: true } }
});
};
⚡ 性能优化规范
7.1 Key 属性规范
typescript
// ✅ Good: 稳定的唯一标识
{users.map(user => (
<UserCard
key={user.id}
user={user}
/>
))}
// 复合 key
{items.map(item => (
<ListItem
key={`${item.type}-${item.id}-${item.version}`}
item={item}
/>
))}
// 动态数据使用唯一标识
{messages.map(message => (
<Message
key={message.id || `temp-${message.timestamp}`}
message={message}
/>
))}
// ❌ Bad: 使用索引作为 key
{users.map((user, index) => (
<UserCard key={index} user={user} />
))}
// ❌ Bad: 不稳定的 key
{products.map(product => (
<ProductCard key={product.name} product={product} /> // name 可能重复
))}
7.2 记忆化优化规范
typescript
// ✅ Good: 适当的记忆化优化
const ExpensiveComponent: React.FC<ExpensiveComponentProps> = React.memo(({
data,
onUpdate,
filters
}) => {
// 记忆化回调函数
const handleUpdate = useCallback((updatedData: Data) => {
onUpdate(updatedData);
}, [onUpdate]);
// 记忆化复杂计算
const processedData = useMemo(() => {
return data
.filter(item => filters.includes(item.category))
.sort((a, b) => a.priority - b.priority)
.map(item => ({
...item,
score: calculateScore(item)
}));
}, [data, filters]);
// 记忆化配置对象
const chartConfig = useMemo(() => ({
type: 'bar' as const,
data: processedData,
options: {
responsive: true,
plugins: {
legend: {
position: 'top' as const,
}
}
}
}), [processedData]);
return (
<div>
<Chart config={chartConfig} />
<DataTable data={processedData} onUpdate={handleUpdate} />
</div>
);
});
// 自定义比较函数
const UserList: React.FC<UserListProps> = React.memo(({ users, selectedUserId }) => {
return (
<div>
{users.map(user => (
<UserItem
key={user.id}
user={user}
isSelected={user.id === selectedUserId}
/>
))}
</div>
);
}, (prevProps, nextProps) => {
// 自定义比较逻辑
return (
prevProps.selectedUserId === nextProps.selectedUserId &&
prevProps.users.length === nextProps.users.length &&
prevProps.users.every((user, index) => user.id === nextProps.users[index].id)
);
});
7.3 代码分割规范
typescript
// ✅ Good: 路由级代码分割
const UserManagement = React.lazy(() =>
import('./pages/UserManagement' /* webpackChunkName: "user-management" */)
);
const ProductManagement = React.lazy(() =>
import('./pages/ProductManagement' /* webpackChunkName: "product-management" */)
);
const Settings = React.lazy(() =>
import('./pages/Settings' /* webpackChunkName: "settings" */)
);
// 统一加载状态
const GlobalLoading: React.FC = () => (
<div className={styles.globalLoading}>
<Spin size="large" />
<p>加载中...</p>
</div>
);
const App: React.FC = () => {
return (
<Router>
<Suspense fallback={<GlobalLoading />}>
<Routes>
<Route path="/users/*" element={<UserManagement />} />
<Route path="/products/*" element={<ProductManagement />} />
<Route path="/settings/*" element={<Settings />} />
</Routes>
</Suspense>
</Router>
);
};
// ✅ Good: 组件级代码分割
const HeavyChart = React.lazy(() => import('./components/HeavyChart'));
const DataGrid = React.lazy(() => import('./components/DataGrid'));
const Dashboard: React.FC = () => {
const [activeTab, setActiveTab] = useState<'chart' | 'table'>('chart');
return (
<div>
<TabSwitcher activeTab={activeTab} onChange={setActiveTab} />
<Suspense fallback={<ChartSkeleton />}>
{activeTab === 'chart' && <HeavyChart />}
</Suspense>
<Suspense fallback={<TableSkeleton />}>
{activeTab === 'table' && <DataGrid />}
</Suspense>
</div>
);
};
// ✅ Good: 预加载策略
const usePreload = (importFn: () => Promise<any>) => {
return useCallback(() => {
importFn();
}, [importFn]);
};
// 在需要的地方预加载
const preloadSettings = usePreload(() => import('./pages/Settings'));
<Link
to="/settings"
onMouseEnter={preloadSettings}
onFocus={preloadSettings}
>
设置
</Link>
🎨 代码风格规范
8.1 导入分组与排序规范
typescript
// ✅ Good: 有序的导入分组
// ========================================
// 1. 外部依赖 (React, 第三方库)
// ========================================
import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { Router, Route, Routes } from 'react-router-dom';
import { Button, Input, Form, message } from 'antd';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
// ========================================
// 2. 类型导入 (使用 import type)
// ========================================
import type { User, Product, ApiResponse } from '@/types';
import type { RootState, AppDispatch } from '@/store';
// ========================================
// 3. 工具函数和配置
// ========================================
import { formatDate, debounce, classNames } from '@/utils/helpers';
import { API_ENDPOINTS, APP_CONFIG } from '@/constants';
import { logger, errorReporter } from '@/utils/logging';
// ========================================
// 4. 服务和 API
// ========================================
import { userApi, productApi } from '@/services/api';
import { authService } from '@/services/auth';
// ========================================
// 5. Store 和状态管理
// ========================================
import { useAppSelector, useAppDispatch } from '@/store/hooks';
import { selectUser, selectIsLoading } from '@/store/slices/userSlice';
// ========================================
// 6. 组件
// ========================================
// 相对路径导入 (当前目录)
import UserAvatar from './UserAvatar';
import UserActions from './UserActions';
// 相对路径导入 (上级目录)
import LoadingSpinner from '../common/LoadingSpinner';
import ErrorBoundary from '../common/ErrorBoundary';
// 绝对路径导入
import { Button, Modal } from '@/components/common';
// ========================================
// 7. 样式和资源
// ========================================
import styles from './UserProfile.module.scss';
import './UserProfile.css'; // 全局样式
// ========================================
// 8. 其他
// ========================================
// 最后导入本地工具函数
const { validateEmail, generateId } = require('./utils');
8.2 注释规范
typescript
// ✅ Good: JSDoc 风格注释
/**
* 用户信息卡片组件
*
* 显示用户基本信息,支持编辑和删除操作
*
* @param props - 组件属性
* @param props.user - 用户数据对象 (必需)
* @param props.onEdit - 编辑用户回调函数
* @param props.onDelete - 删除用户回调函数
* @param props.variant - 显示变体,控制布局和样式
* @param props.isSelected - 是否被选中状态
* @param props.className - 自定义 CSS 类名
*
* @example
* ```tsx
* <UserCard
* user={userData}
* onEdit={handleEdit}
* onDelete={handleDelete}
* variant="detailed"
* isSelected={true}
* className="custom-card"
* />
* ```
*
* @throws {Error} 当 user 属性未提供时抛出错误
*
* @returns 用户卡片 React 元素
*/
const UserCard: React.FC<UserCardProps> = ({
user,
onEdit,
onDelete,
variant = 'compact',
isSelected = false,
className = ''
}) => {
if (!user) {
throw new Error('UserCard: user prop is required');
}
// 处理编辑操作
const handleEdit = useCallback(() => {
onEdit?.(user);
}, [user, onEdit]);
// 处理删除操作
const handleDelete = useCallback(() => {
if (window.confirm(`确定要删除用户 ${user.name} 吗?`)) {
onDelete?.(user.id);
}
}, [user, onDelete]);
return (
<div className={classNames(styles.card, className)}>
{/* 组件实现 */}
</div>
);
};
// ✅ Good: 行内注释
const calculatePrice = (basePrice: number, discount: number) => {
// 确保折扣在 0-1 范围内
const validDiscount = Math.max(0, Math.min(1, discount));
// 计算最终价格(四舍五入到两位小数)
const finalPrice = basePrice * (1 - validDiscount);
return Math.round(finalPrice * 100) / 100;
};
8.3 错误边界规范
typescript
// ✅ Good: 完整的错误边界实现
interface ErrorBoundaryState {
hasError: boolean;
error?: Error;
errorInfo?: React.ErrorInfo;
}
interface ErrorBoundaryProps {
children: React.ReactNode;
fallback?: React.ComponentType<{ error: Error; resetError: () => void }>;
onError?: (error: Error, errorInfo: React.ErrorInfo) => void;
}
/**
* 错误边界组件
*
* 捕获子组件树的 JavaScript 错误,显示降级 UI
*
* @see https://reactjs.org/docs/error-boundaries.html
*/
class ErrorBoundary extends React.Component<ErrorBoundaryProps, ErrorBoundaryState> {
constructor(props: ErrorBoundaryProps) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error: Error): ErrorBoundaryState {
// 更新 state 使下一次渲染能够显示降级 UI
return { hasError: true, error };
}
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
// 记录错误信息
console.error('Error caught by boundary:', error, errorInfo);
// 上报错误服务
this.props.onError?.(error, errorInfo);
errorReporter.captureException(error, {
extra: errorInfo,
tags: { component: 'ErrorBoundary' }
});
this.setState({
error,
errorInfo
});
}
// 重置错误状态
resetError = () => {
this.setState({
hasError: false,
error: undefined,
errorInfo: undefined
});
};
render() {
if (this.state.hasError) {
// 自定义降级 UI
if (this.props.fallback) {
const FallbackComponent = this.props.fallback;
return <FallbackComponent error={this.state.error!} resetError={this.resetError} />;
}
// 默认降级 UI
return (
<div className={styles.errorFallback}>
<div className={styles.errorContent}>
<h2>😵 出错了</h2>
<p>抱歉,发生了意外错误</p>
{process.env.NODE_ENV === 'development' && this.state.error && (
<details className={styles.errorDetails}>
<summary>错误详情 (开发模式)</summary>
<pre>{this.state.error.stack}</pre>
{this.state.errorInfo && (
<pre>{this.state.errorInfo.componentStack}</pre>
)}
</details>
)}
<div className={styles.errorActions}>
<button
onClick={this.resetError}
className={styles.retryButton}
>
重试
</button>
<button
onClick={() => window.location.reload()}
className={styles.reloadButton}
>
刷新页面
</button>
</div>
</div>
</div>
);
}
return this.props.children;
}
}
// 使用示例
const App: React.FC = () => {
const handleError = (error: Error, errorInfo: React.ErrorInfo) => {
// 自定义错误处理逻辑
analytics.track('ReactError', {
message: error.message,
componentStack: errorInfo.componentStack
});
};
return (
<ErrorBoundary
fallback={CustomErrorFallback}
onError={handleError}
>
<Router>
<AppRoutes />
</Router>
</ErrorBoundary>
);
};
📊 总结与工具推荐
核心原则总结
- 一致性:团队统一编码风格,降低认知成本
- 可读性:代码自解释,减少注释需求
- 可维护性:易于修改和扩展,降低技术债务
- 类型安全:充分利用 TypeScript,减少运行时错误
- 性能友好:避免不必要的重渲染和内存泄漏
- 团队协作:统一的规范便于代码审查和知识传递
必备工具配置
json
// .eslintrc.js
module.exports = {
extends: [
'eslint:recommended',
'@typescript-eslint/recommended',
'plugin:react/recommended',
'plugin:react-hooks/recommended',
'plugin:jsx-a11y/recommended'
],
rules: {
'react/prop-types': 'off', // TypeScript 处理类型检查
'@typescript-eslint/explicit-function-return-type': 'warn',
'@typescript-eslint/no-explicit-any': 'warn',
'jsx-a11y/anchor-is-valid': 'error'
}
};
// .prettierrc.js
module.exports = {
singleQuote: true,
trailingComma: 'es5',
printWidth: 80,
tabWidth: 2,
semi: true,
bracketSpacing: true,
arrowParens: 'avoid',
endOfLine: 'lf'
};
// package.json scripts
{
"scripts": {
"lint": "eslint src --ext .ts,.tsx",
"lint:fix": "eslint src --ext .ts,.tsx --fix",
"format": "prettier --write src/**/*.{ts,tsx,json,css,scss}",
"type-check": "tsc --noEmit",
"pre-commit": "lint-staged"
}
}
// lint-staged.config.js
module.exports = {
'*.{ts,tsx}': [
'eslint --fix',
'prettier --write'
],
'*.{json,css,scss,md}': [
'prettier --write'
]
};
文件模板示例
typescript
// components/ComponentName.tsx
import React from 'react';
import type { ComponentNameProps } from './types';
import styles from './ComponentName.module.scss';
/**
* 组件描述
*
* @param props - 组件属性
*/
const ComponentName: React.FC<ComponentNameProps> = ({
// 属性定义
}) => {
// 状态和逻辑
return (
// JSX
);
};
export default ComponentName;
// hooks/useFeatureName.ts
import { useState, useEffect } from 'react';
import type { UseFeatureNameReturn, UseFeatureNameOptions } from './types';
/**
* Hook 描述
*
* @param options - 配置选项
*/
export function useFeatureName(options: UseFeatureNameOptions = {}): UseFeatureNameReturn {
// Hook 实现
return {
// 返回值
};
}
遵循这些规范,你的 React + TypeScript 代码将具备:
- ✅ 清晰的组件结构和职责分离
- ✅ 严格的类型约束和编译时检查
- ✅ 优秀的运行时性能和用户体验
- ✅ 良好的团队协作和代码审查体验
- ✅ 易于维护、测试和扩展的代码基础
规范不是束缚,而是提升开发效率、降低维护成本的基石。让每一行代码都成为艺术品,而非负担。