React + TypeScript 企业级编码规范指南

🎯 为什么需要编码规范?

问题现状

  • 没有规范 → 代码像"杂乱的拼图",难以拼接和维护
  • 风格混乱 → 团队协作像"多国语言会议",沟通成本极高
  • 类型滥用 → 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>
  );
};

📊 总结与工具推荐

核心原则总结

  1. 一致性:团队统一编码风格,降低认知成本
  2. 可读性:代码自解释,减少注释需求
  3. 可维护性:易于修改和扩展,降低技术债务
  4. 类型安全:充分利用 TypeScript,减少运行时错误
  5. 性能友好:避免不必要的重渲染和内存泄漏
  6. 团队协作:统一的规范便于代码审查和知识传递

必备工具配置

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 代码将具备:

  • ✅ 清晰的组件结构和职责分离
  • ✅ 严格的类型约束和编译时检查
  • ✅ 优秀的运行时性能和用户体验
  • ✅ 良好的团队协作和代码审查体验
  • ✅ 易于维护、测试和扩展的代码基础

规范不是束缚,而是提升开发效率、降低维护成本的基石。让每一行代码都成为艺术品,而非负担。

相关推荐
lapiii3583 小时前
快速学完React计划(第一天)
前端·react.js·前端框架
LRH4 小时前
时间切片 + 双工作循环 + 优先级模型:React 的并发任务管理策略
前端·react.js
歪歪1004 小时前
React Native开发有哪些优势和劣势?
服务器·前端·javascript·react native·react.js·前端框架
XiaoSong5 小时前
React Native 主题配置终极指南,一篇文章说透
前端·react native·react.js
油条不卖5 小时前
本地window10同步ubuntu上conda指定环境,并在C#项目中通过Python.NET调用自定义python接口
linux·ubuntu·conda
运维老司机7 小时前
ThinkPad 安装 Ubuntu 系统教程
linux·运维·ubuntu
im_AMBER18 小时前
React 01
前端·javascript·笔记·react.js·前端框架·web
@大迁世界19 小时前
React 19.2.0 有哪些新变化
前端·javascript·react.js·前端框架·ecmascript
安卓开发者19 小时前
鸿蒙NEXT Wear Engine穿戴侧应用开发完全指南
ubuntu·华为·harmonyos