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

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

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

相关推荐
人工智能训练1 小时前
如何在 Ubuntu 22.04 中安装 Docker 引擎和 Linux 版 Docker Desktop 桌面软件
linux·运维·服务器·数据库·ubuntu·docker·ai编程
三五度6 小时前
vmware的ubuntu20.04无网络图标
linux·ubuntu
骑自行车的码农7 小时前
React SSR 技术实现原理
算法·react.js
亮子AI7 小时前
【Typescript】一句赋值语句,为什么有两个 const?
typescript
QT 小鲜肉9 小时前
【个人成长笔记】将Try Ubuntu里面配置好的文件系统克隆在U盘上(创建一个带有持久化功能的Ubuntu Live USB系统)
linux·开发语言·数据库·笔记·ubuntu
Cxiaomu11 小时前
React Native App 自动检测版本更新完整实现指南
javascript·react native·react.js
春风霓裳12 小时前
ubuntu磁盘管理、磁盘扩容
linux·运维·ubuntu
Kingsaj13 小时前
uni-app打包app -- 在用户首次启动 App 时,强制弹出一个“用户协议与隐私政策”的确认对话框。
服务器·ubuntu·uni-app
小p13 小时前
react学习6:受控组件
前端·react.js
cv咸鱼仔14 小时前
关于TS类型系统中的协变与逆变
typescript