Published on
· 24 min read

前端代码设计最佳实践指南

Authors
  • avatar
    Name
    felixDu
    Twitter
Table of Contents

前端代码设计最佳实践指南

目录

  1. 项目架构设计原则
  2. 目录结构设计
  3. 组件设计模式
  4. 复用组件设计思想
  5. 状态管理策略
  6. 代码规范与质量
  7. 性能优化策略
  8. 类型安全
  9. 测试策略
  10. 部署与构建

项目架构设计原则

1. 单一职责原则 (SRP)

// ❌ 违反单一职责
const UserComponent = () => {
  // 数据获取
  // 数据处理
  // UI渲染
  // 表单验证
  // 状态管理
}

// ✅ 遵循单一职责
const useUserData = () => { /* 数据逻辑 */ }
const useUserValidation = () => { /* 验证逻辑 */ }
const UserForm = () => { /* UI渲染 */ }
const UserProfile = () => { /* 组合使用 */ }

2. 开闭原则 (OCP)

// ✅ 对扩展开放,对修改封闭
interface TableColumn<T = any> {
  key: string;
  title: string;
  render?: (value: any, record: T) => React.ReactNode;
  sorter?: boolean;
  filters?: Array<{ text: string; value: any }>;
}

const Table = <T,>({ columns, dataSource }: TableProps<T>) => {
  // 基础表格逻辑,通过 columns 配置扩展功能
}

3. 依赖倒置原则 (DIP)

// ✅ 依赖抽象而非具体实现
interface ApiClient {
  get<T>(url: string): Promise<T>;
  post<T>(url: string, data: any): Promise<T>;
}

const useUserService = (apiClient: ApiClient) => {
  return {
    getUsers: () => apiClient.get('/users'),
    createUser: (userData: User) => apiClient.post('/users', userData)
  }
}

目录结构设计

标准B端项目结构

src/
├── components/           # 通用组件
│   ├── ui/              # 基础UI组件
│   │   ├── Button/
│   │   ├── Input/
│   │   ├── Modal/
│   │   └── index.ts
│   ├── business/        # 业务组件
│   │   ├── UserForm/
│   │   ├── DataTable/
│   │   └── index.ts
│   └── layout/          # 布局组件
│       ├── Header/
│       ├── Sidebar/
│       └── index.ts
├── pages/               # 页面组件
│   ├── Dashboard/
│   ├── Users/
│   └── Settings/
├── hooks/               # 自定义Hook
│   ├── useApi.ts
│   ├── useAuth.ts
│   └── index.ts
├── services/            # API服务
│   ├── api/
│   ├── auth/
│   └── index.ts
├── store/               # 状态管理
│   ├── slices/
│   ├── middleware/
│   └── index.ts
├── utils/               # 工具函数
│   ├── format/
│   ├── validation/
│   └── index.ts
├── types/               # 类型定义
│   ├── api.ts
│   ├── user.ts
│   └── index.ts
├── constants/           # 常量定义
├── assets/              # 静态资源
└── styles/              # 样式文件

组件文件结构

Button/
├── index.ts             # 导出
├── Button.tsx           # 主组件
├── Button.types.ts      # 类型定义
├── Button.styles.ts     # 样式定义
├── Button.test.tsx      # 测试文件
├── Button.stories.tsx   # Storybook文档
└── hooks/               # 组件专用Hook
    └── useButtonState.ts

组件设计模式

1. 容器组件模式

// Container Component - 负责数据和逻辑
const UserListContainer: React.FC = () => {
  const { data, loading, error } = useUsers();
  const { deleteUser } = useUserActions();

  return (
    <UserList
      users={data}
      loading={loading}
      error={error}
      onDelete={deleteUser}
    />
  );
};

// Presentational Component - 负责UI渲染
const UserList: React.FC<UserListProps> = ({
  users,
  loading,
  error,
  onDelete
}) => {
  if (loading) return <Loading />;
  if (error) return <Error message={error} />;

  return (
    <div>
      {users.map(user => (
        <UserCard
          key={user.id}
          user={user}
          onDelete={() => onDelete(user.id)}
        />
      ))}
    </div>
  );
};

2. 高阶组件模式

// HOC for authentication
const withAuth = <P extends object>(Component: React.ComponentType<P>) => {
  return (props: P) => {
    const { user, loading } = useAuth();

    if (loading) return <Loading />;
    if (!user) return <Redirect to="/login" />;

    return <Component {...props} />;
  };
};

// 使用
const ProtectedDashboard = withAuth(Dashboard);

3. Render Props模式

interface DataFetcherProps<T> {
  url: string;
  children: (data: {
    data: T | null;
    loading: boolean;
    error: string | null;
    refetch: () => void;
  }) => React.ReactNode;
}

const DataFetcher = <T,>({ url, children }: DataFetcherProps<T>) => {
  const { data, loading, error, refetch } = useApi<T>(url);

  return <>{children({ data, loading, error, refetch })}</>;
};

// 使用
<DataFetcher<User[]> url="/api/users">
  {({ data, loading, error }) => (
    loading ? <Loading /> : <UserList users={data} />
  )}
</DataFetcher>

4. 复合组件模式

// 主组件
const Card: React.FC<CardProps> & {
  Header: typeof CardHeader;
  Body: typeof CardBody;
  Footer: typeof CardFooter;
} = ({ children, ...props }) => {
  return <div className="card" {...props}>{children}</div>;
};

// 子组件
const CardHeader: React.FC<CardHeaderProps> = ({ children }) => (
  <div className="card-header">{children}</div>
);

const CardBody: React.FC<CardBodyProps> = ({ children }) => (
  <div className="card-body">{children}</div>
);

const CardFooter: React.FC<CardFooterProps> = ({ children }) => (
  <div className="card-footer">{children}</div>
);

// 组合
Card.Header = CardHeader;
Card.Body = CardBody;
Card.Footer = CardFooter;

// 使用
<Card>
  <Card.Header>标题</Card.Header>
  <Card.Body>内容</Card.Body>
  <Card.Footer>操作按钮</Card.Footer>
</Card>

复用组件设计思想

1. 基础组件设计原则

A. 单一职责与最小API

// ✅ 职责单一的按钮组件
interface ButtonProps {
  variant?: 'primary' | 'secondary' | 'danger';
  size?: 'small' | 'medium' | 'large';
  disabled?: boolean;
  loading?: boolean;
  children: React.ReactNode;
  onClick?: (event: React.MouseEvent) => void;
}

const Button: React.FC<ButtonProps> = ({
  variant = 'primary',
  size = 'medium',
  disabled = false,
  loading = false,
  children,
  onClick,
  ...restProps
}) => {
  return (
    <button
      className={`btn btn--${variant} btn--${size}`}
      disabled={disabled || loading}
      onClick={onClick}
      {...restProps}
    >
      {loading ? <Spinner /> : children}
    </button>
  );
};

B. 可扩展的表单组件

interface FormFieldProps {
  label?: string;
  error?: string;
  required?: boolean;
  helpText?: string;
  children: React.ReactNode;
}

const FormField: React.FC<FormFieldProps> = ({
  label,
  error,
  required,
  helpText,
  children
}) => {
  return (
    <div className="form-field">
      {label && (
        <label className="form-field__label">
          {label}
          {required && <span className="required">*</span>}
        </label>
      )}
      <div className="form-field__control">
        {children}
      </div>
      {helpText && <div className="form-field__help">{helpText}</div>}
      {error && <div className="form-field__error">{error}</div>}
    </div>
  );
};

// 使用示例
<FormField label="用户名" required error={errors.username}>
  <Input
    value={username}
    onChange={setUsername}
    placeholder="请输入用户名"
  />
</FormField>

2. 高度复用的业务组件

A. 数据表格组件

interface TableColumn<T = any> {
  key: keyof T | string;
  title: string;
  width?: number;
  align?: 'left' | 'center' | 'right';
  fixed?: 'left' | 'right';
  sorter?: boolean | ((a: T, b: T) => number);
  filters?: Array<{ text: string; value: any }>;
  render?: (value: any, record: T, index: number) => React.ReactNode;
}

interface TableProps<T = any> {
  columns: TableColumn<T>[];
  dataSource: T[];
  rowKey?: keyof T | ((record: T) => string);
  loading?: boolean;
  pagination?: {
    current: number;
    pageSize: number;
    total: number;
    onChange: (page: number, pageSize: number) => void;
  };
  onRowSelect?: (selectedRows: T[]) => void;
  expandable?: {
    expandedRowRender: (record: T) => React.ReactNode;
    rowExpandable?: (record: T) => boolean;
  };
}

const Table = <T,>({
  columns,
  dataSource,
  rowKey = 'id',
  loading = false,
  pagination,
  onRowSelect,
  expandable
}: TableProps<T>) => {
  // 表格实现逻辑
  const [selectedRows, setSelectedRows] = useState<T[]>([]);
  const [sortConfig, setSortConfig] = useState<{
    key: string;
    direction: 'asc' | 'desc';
  } | null>(null);

  // 渲染逻辑
  return (
    <div className="custom-table">
      {/* 表格头部 */}
      <thead>
        {columns.map(column => (
          <th key={column.key as string}>
            {column.title}
            {column.sorter && <SortIcon />}
          </th>
        ))}
      </thead>
      {/* 表格内容 */}
      <tbody>
        {dataSource.map((record, index) => (
          <tr key={typeof rowKey === 'function' ? rowKey(record) : record[rowKey]}>
            {columns.map(column => (
              <td key={column.key as string}>
                {column.render
                  ? column.render(record[column.key], record, index)
                  : record[column.key]
                }
              </td>
            ))}
          </tr>
        ))}
      </tbody>
    </div>
  );
};

B. 搜索表单组件

interface SearchFormItem {
  key: string;
  label: string;
  type: 'input' | 'select' | 'dateRange' | 'cascader';
  props?: Record<string, any>;
  rules?: ValidationRule[];
}

interface SearchFormProps {
  items: SearchFormItem[];
  onSearch: (values: Record<string, any>) => void;
  onReset?: () => void;
  loading?: boolean;
  initialValues?: Record<string, any>;
}

const SearchForm: React.FC<SearchFormProps> = ({
  items,
  onSearch,
  onReset,
  loading,
  initialValues
}) => {
  const [form] = useForm();

  const handleSubmit = async () => {
    try {
      const values = await form.validateFields();
      onSearch(values);
    } catch (error) {
      console.error('表单验证失败:', error);
    }
  };

  const handleReset = () => {
    form.resetFields();
    onReset?.();
  };

  return (
    <Form
      form={form}
      layout="inline"
      initialValues={initialValues}
      onFinish={handleSubmit}
    >
      {items.map(item => (
        <Form.Item
          key={item.key}
          name={item.key}
          label={item.label}
          rules={item.rules}
        >
          {renderFormControl(item)}
        </Form.Item>
      ))}
      <Form.Item>
        <Button type="primary" htmlType="submit" loading={loading}>
          搜索
        </Button>
        <Button onClick={handleReset}>
          重置
        </Button>
      </Form.Item>
    </Form>
  );
};

3. Hook复用设计

A. 数据获取Hook

interface UseApiOptions<T> {
  initialData?: T;
  dependencies?: any[];
  onSuccess?: (data: T) => void;
  onError?: (error: Error) => void;
  transform?: (data: any) => T;
}

const useApi = <T>(
  url: string | null,
  options: UseApiOptions<T> = {}
) => {
  const [data, setData] = useState<T | null>(options.initialData || null);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState<Error | null>(null);

  const fetchData = useCallback(async () => {
    if (!url) return;

    try {
      setLoading(true);
      setError(null);

      const response = await fetch(url);
      const result = await response.json();

      const transformedData = options.transform
        ? options.transform(result)
        : result;

      setData(transformedData);
      options.onSuccess?.(transformedData);
    } catch (err) {
      const error = err as Error;
      setError(error);
      options.onError?.(error);
    } finally {
      setLoading(false);
    }
  }, [url, ...options.dependencies || []]);

  useEffect(() => {
    fetchData();
  }, [fetchData]);

  return { data, loading, error, refetch: fetchData };
};

B. 表单Hook

interface UseFormConfig<T> {
  initialValues?: Partial<T>;
  validationRules?: Record<keyof T, ValidationRule[]>;
  onSubmit?: (values: T) => Promise<void> | void;
}

const useForm = <T extends Record<string, any>>(
  config: UseFormConfig<T> = {}
) => {
  const [values, setValues] = useState<Partial<T>>(config.initialValues || {});
  const [errors, setErrors] = useState<Partial<Record<keyof T, string>>>({});
  const [touched, setTouched] = useState<Partial<Record<keyof T, boolean>>>({});
  const [submitting, setSubmitting] = useState(false);

  const setValue = useCallback((key: keyof T, value: any) => {
    setValues(prev => ({ ...prev, [key]: value }));
    setTouched(prev => ({ ...prev, [key]: true }));

    // 实时验证
    if (config.validationRules?.[key]) {
      const error = validateField(value, config.validationRules[key]);
      setErrors(prev => ({ ...prev, [key]: error }));
    }
  }, [config.validationRules]);

  const submit = useCallback(async () => {
    try {
      setSubmitting(true);

      // 全量验证
      const allErrors = validateAllFields(values, config.validationRules);
      if (Object.keys(allErrors).length > 0) {
        setErrors(allErrors);
        return;
      }

      await config.onSubmit?.(values as T);
    } finally {
      setSubmitting(false);
    }
  }, [values, config.validationRules, config.onSubmit]);

  const reset = useCallback(() => {
    setValues(config.initialValues || {});
    setErrors({});
    setTouched({});
  }, [config.initialValues]);

  return {
    values,
    errors,
    touched,
    submitting,
    setValue,
    submit,
    reset,
    isValid: Object.keys(errors).length === 0
  };
};

状态管理策略

1. 状态分层管理

// 全局状态 - Redux Toolkit
interface GlobalState {
  user: UserState;
  app: AppState;
  cache: CacheState;
}

// 页面级状态 - useState/useReducer
const UserManagementPage = () => {
  const [searchParams, setSearchParams] = useState({});
  const [selectedUsers, setSelectedUsers] = useState([]);

  // 全局用户信息
  const currentUser = useSelector(state => state.user.current);

  return (
    // 页面内容
  );
};

// 组件级状态 - 内部状态
const Modal = ({ visible, onClose }) => {
  const [step, setStep] = useState(1);
  const [formData, setFormData] = useState({});

  return (
    // 模态框内容
  );
};

2. 状态归一化设计

// ✅ 归一化的状态结构
interface NormalizedState<T> {
  ids: string[];
  entities: Record<string, T>;
  loading: boolean;
  error: string | null;
}

const usersSlice = createSlice({
  name: 'users',
  initialState: {
    ids: [],
    entities: {},
    loading: false,
    error: null
  } as NormalizedState<User>,
  reducers: {
    setUsers: (state, action) => {
      const users = action.payload;
      state.ids = users.map(user => user.id);
      state.entities = users.reduce((acc, user) => {
        acc[user.id] = user;
        return acc;
      }, {});
    },
    updateUser: (state, action) => {
      const user = action.payload;
      state.entities[user.id] = user;
    }
  }
});

3. 服务层设计

// API服务基类
abstract class BaseApiService {
  protected baseURL: string;

  constructor(baseURL: string) {
    this.baseURL = baseURL;
  }

  protected async request<T>(
    endpoint: string,
    options: RequestInit = {}
  ): Promise<T> {
    const url = `${this.baseURL}${endpoint}`;

    try {
      const response = await fetch(url, {
        ...options,
        headers: {
          'Content-Type': 'application/json',
          ...options.headers,
        }
      });

      if (!response.ok) {
        throw new Error(`API Error: ${response.status}`);
      }

      return await response.json();
    } catch (error) {
      throw this.handleError(error);
    }
  }

  protected handleError(error: any): Error {
    // 统一错误处理
    return error;
  }
}

// 具体服务实现
class UserService extends BaseApiService {
  constructor() {
    super('/api');
  }

  async getUsers(params?: UserQueryParams): Promise<User[]> {
    return this.request('/users', {
      method: 'GET',
      // 处理查询参数
    });
  }

  async createUser(userData: CreateUserRequest): Promise<User> {
    return this.request('/users', {
      method: 'POST',
      body: JSON.stringify(userData)
    });
  }

  async updateUser(id: string, userData: UpdateUserRequest): Promise<User> {
    return this.request(`/users/${id}`, {
      method: 'PUT',
      body: JSON.stringify(userData)
    });
  }

  async deleteUser(id: string): Promise<void> {
    return this.request(`/users/${id}`, {
      method: 'DELETE'
    });
  }
}

代码规范与质量

1. 命名规范

// ✅ 良好的命名
// 组件 - PascalCase
const UserProfileCard = () => {};

// Hook - use开头的camelCase
const useUserProfile = () => {};

// 常量 - SCREAMING_SNAKE_CASE
const API_ENDPOINTS = {
  USERS: '/api/users',
  ROLES: '/api/roles'
};

// 函数 - camelCase,动词开头
const handleUserClick = () => {};
const validateUserInput = () => {};
const formatUserName = () => {};

// 类型 - PascalCase,有意义的后缀
interface UserProfileProps {}
type UserActionType = 'CREATE' | 'UPDATE' | 'DELETE';
enum UserStatus {
  ACTIVE = 'active',
  INACTIVE = 'inactive',
  PENDING = 'pending'
}

2. 代码组织

// ✅ 清晰的代码结构
const UserManagement: React.FC = () => {
  // 1. Hooks (按依赖顺序)
  const { user } = useAuth();
  const { users, loading, error } = useUsers();
  const { createUser, updateUser, deleteUser } = useUserActions();

  // 2. 状态
  const [selectedUsers, setSelectedUsers] = useState<User[]>([]);
  const [searchParams, setSearchParams] = useState<UserSearchParams>({});

  // 3. 计算值
  const filteredUsers = useMemo(() =>
    users.filter(user => user.name.includes(searchParams.keyword || '')),
    [users, searchParams.keyword]
  );

  // 4. 事件处理器
  const handleSearch = useCallback((params: UserSearchParams) => {
    setSearchParams(params);
  }, []);

  const handleUserSelect = useCallback((users: User[]) => {
    setSelectedUsers(users);
  }, []);

  // 5. 副作用
  useEffect(() => {
    // 组件挂载后的逻辑
  }, []);

  // 6. 早期返回
  if (error) {
    return <ErrorMessage error={error} />;
  }

  // 7. 主要渲染
  return (
    <div className="user-management">
      <SearchForm onSearch={handleSearch} />
      <UserTable
        users={filteredUsers}
        loading={loading}
        selectedUsers={selectedUsers}
        onUserSelect={handleUserSelect}
        onEdit={updateUser}
        onDelete={deleteUser}
      />
    </div>
  );
};

3. 错误处理策略

// 错误边界组件
class ErrorBoundary extends React.Component<
  { children: React.ReactNode; fallback?: React.ComponentType<{ error: Error }> },
  { hasError: boolean; error: Error | null }
> {
  constructor(props) {
    super(props);
    this.state = { hasError: false, error: null };
  }

  static getDerivedStateFromError(error: Error) {
    return { hasError: true, error };
  }

  componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
    console.error('Error caught by boundary:', error, errorInfo);
    // 发送到错误监控服务
    ErrorService.reportError(error, errorInfo);
  }

  render() {
    if (this.state.hasError) {
      const FallbackComponent = this.props.fallback || DefaultErrorFallback;
      return <FallbackComponent error={this.state.error!} />;
    }

    return this.props.children;
  }
}

// Hook级错误处理
const useErrorHandler = () => {
  const showNotification = useNotification();

  return useCallback((error: Error, context?: string) => {
    console.error(`Error in ${context}:`, error);

    // 用户友好的错误提示
    const userMessage = getUserFriendlyErrorMessage(error);
    showNotification({
      type: 'error',
      message: userMessage
    });

    // 上报错误
    ErrorService.reportError(error, { context });
  }, [showNotification]);
};

性能优化策略

1. 组件级优化

// ✅ 使用React.memo优化重渲染
const UserCard = React.memo<UserCardProps>(({ user, onEdit, onDelete }) => {
  return (
    <div className="user-card">
      <div className="user-info">
        <h3>{user.name}</h3>
        <p>{user.email}</p>
      </div>
      <div className="user-actions">
        <Button onClick={() => onEdit(user)}>编辑</Button>
        <Button onClick={() => onDelete(user.id)}>删除</Button>
      </div>
    </div>
  );
}, (prevProps, nextProps) => {
  // 自定义比较函数
  return (
    prevProps.user.id === nextProps.user.id &&
    prevProps.user.updatedAt === nextProps.user.updatedAt
  );
});

// ✅ 使用useMemo缓存计算结果
const ExpensiveComponent: React.FC<{ items: Item[] }> = ({ items }) => {
  const expensiveValue = useMemo(() => {
    return items.reduce((acc, item) => {
      // 复杂计算
      return acc + complexCalculation(item);
    }, 0);
  }, [items]);

  const filteredItems = useMemo(() =>
    items.filter(item => item.active),
    [items]
  );

  return <div>{expensiveValue}</div>;
};

// ✅ 使用useCallback缓存函数
const ParentComponent = () => {
  const [users, setUsers] = useState<User[]>([]);

  const handleUserUpdate = useCallback((userId: string, updates: Partial<User>) => {
    setUsers(prev => prev.map(user =>
      user.id === userId ? { ...user, ...updates } : user
    ));
  }, []);

  return (
    <div>
      {users.map(user => (
        <UserCard
          key={user.id}
          user={user}
          onUpdate={handleUserUpdate} // 稳定的引用
        />
      ))}
    </div>
  );
};

2. 虚拟滚动实现

interface VirtualListProps<T> {
  items: T[];
  itemHeight: number;
  containerHeight: number;
  renderItem: (item: T, index: number) => React.ReactNode;
  overscan?: number;
}

const VirtualList = <T,>({
  items,
  itemHeight,
  containerHeight,
  renderItem,
  overscan = 5
}: VirtualListProps<T>) => {
  const [scrollTop, setScrollTop] = useState(0);

  const visibleRange = useMemo(() => {
    const start = Math.max(0, Math.floor(scrollTop / itemHeight) - overscan);
    const visibleCount = Math.ceil(containerHeight / itemHeight);
    const end = Math.min(items.length, start + visibleCount + overscan * 2);

    return { start, end };
  }, [scrollTop, itemHeight, containerHeight, overscan, items.length]);

  const totalHeight = items.length * itemHeight;
  const offsetY = visibleRange.start * itemHeight;

  const handleScroll = useCallback((e: React.UIEvent<HTMLDivElement>) => {
    setScrollTop(e.currentTarget.scrollTop);
  }, []);

  return (
    <div
      style={{ height: containerHeight, overflow: 'auto' }}
      onScroll={handleScroll}
    >
      <div style={{ height: totalHeight, position: 'relative' }}>
        <div style={{ transform: `translateY(${offsetY}px)` }}>
          {items.slice(visibleRange.start, visibleRange.end).map((item, index) =>
            renderItem(item, visibleRange.start + index)
          )}
        </div>
      </div>
    </div>
  );
};

3. 代码分割与懒加载

// 路由级代码分割
const Dashboard = React.lazy(() => import('../pages/Dashboard'));
const UserManagement = React.lazy(() => import('../pages/UserManagement'));
const Settings = React.lazy(() => import('../pages/Settings'));

const AppRouter = () => (
  <Router>
    <Suspense fallback={<PageLoading />}>
      <Routes>
        <Route path="/dashboard" element={<Dashboard />} />
        <Route path="/users" element={<UserManagement />} />
        <Route path="/settings" element={<Settings />} />
      </Routes>
    </Suspense>
  </Router>
);

// 组件级懒加载
const LazyModal = React.lazy(() => import('./Modal'));

const useModal = () => {
  const [visible, setVisible] = useState(false);

  const Modal = useMemo(() => {
    if (!visible) return null;

    return (
      <Suspense fallback={<div>加载中...</div>}>
        <LazyModal visible={visible} onClose={() => setVisible(false)} />
      </Suspense>
    );
  }, [visible]);

  return { Modal, show: () => setVisible(true), hide: () => setVisible(false) };
};

类型安全

1. 严格的类型定义

// 基础实体类型
interface User {
  readonly id: string;
  name: string;
  email: string;
  role: UserRole;
  status: UserStatus;
  createdAt: Date;
  updatedAt: Date;
}

// API请求类型
interface CreateUserRequest extends Omit<User, 'id' | 'createdAt' | 'updatedAt'> {}

interface UpdateUserRequest extends Partial<Omit<User, 'id' | 'createdAt' | 'updatedAt'>> {}

// API响应类型
interface ApiResponse<T> {
  data: T;
  message: string;
  success: boolean;
  timestamp: number;
}

// 分页类型
interface PaginatedResponse<T> extends ApiResponse<T[]> {
  pagination: {
    current: number;
    pageSize: number;
    total: number;
  };
}

2. 泛型组件设计

// 泛型表单组件
interface FormProps<T> {
  initialValues?: Partial<T>;
  onSubmit: (values: T) => Promise<void>;
  validationSchema?: ValidationSchema<T>;
  children: (formHelpers: FormHelpers<T>) => React.ReactNode;
}

interface FormHelpers<T> {
  values: Partial<T>;
  errors: Partial<Record<keyof T, string>>;
  setValue: <K extends keyof T>(key: K, value: T[K]) => void;
  setError: <K extends keyof T>(key: K, error: string) => void;
  submit: () => Promise<void>;
  reset: () => void;
}

const Form = <T,>({ initialValues, onSubmit, validationSchema, children }: FormProps<T>) => {
  // 表单逻辑实现
  const formHelpers: FormHelpers<T> = {
    // ...实现
  };

  return <form>{children(formHelpers)}</form>;
};

// 使用示例
<Form<User>
  initialValues={{ name: '', email: '' }}
  onSubmit={handleSubmit}
  validationSchema={userValidationSchema}
>
  {({ values, errors, setValue }) => (
    <>
      <Input
        value={values.name}
        onChange={value => setValue('name', value)}
        error={errors.name}
      />
      <Input
        value={values.email}
        onChange={value => setValue('email', value)}
        error={errors.email}
      />
    </>
  )}
</Form>

测试策略

1. 单元测试

// 组件测试
describe('UserCard', () => {
  const mockUser: User = {
    id: '1',
    name: 'John Doe',
    email: 'john@example.com',
    role: 'admin'
  };

  it('应该正确渲染用户信息', () => {
    render(<UserCard user={mockUser} />);

    expect(screen.getByText('John Doe')).toBeInTheDocument();
    expect(screen.getByText('john@example.com')).toBeInTheDocument();
  });

  it('应该处理编辑操作', async () => {
    const onEdit = jest.fn();
    render(<UserCard user={mockUser} onEdit={onEdit} />);

    const editButton = screen.getByRole('button', { name: /编辑/ });
    await userEvent.click(editButton);

    expect(onEdit).toHaveBeenCalledWith(mockUser);
  });
});

// Hook测试
describe('useUsers', () => {
  it('应该正确获取用户列表', async () => {
    const { result, waitForNextUpdate } = renderHook(() => useUsers());

    expect(result.current.loading).toBe(true);

    await waitForNextUpdate();

    expect(result.current.loading).toBe(false);
    expect(result.current.data).toEqual(mockUsers);
  });
});

2. 集成测试

// 页面级集成测试
describe('UserManagement Page', () => {
  beforeEach(() => {
    // 模拟API
    mockApiResponse('/api/users', mockUsers);
  });

  it('应该完成用户管理流程', async () => {
    render(<UserManagement />);

    // 等待数据加载
    await waitFor(() => {
      expect(screen.getByText('用户列表')).toBeInTheDocument();
    });

    // 测试搜索功能
    const searchInput = screen.getByPlaceholderText('搜索用户');
    await userEvent.type(searchInput, 'John');

    expect(screen.getByText('John Doe')).toBeInTheDocument();
    expect(screen.queryByText('Jane Smith')).not.toBeInTheDocument();

    // 测试编辑功能
    const editButton = screen.getAllByText('编辑')[0];
    await userEvent.click(editButton);

    expect(screen.getByText('编辑用户')).toBeInTheDocument();
  });
});

工程化配置

1. ESLint配置

// .eslintrc.js
module.exports = {
  extends: [
    '@typescript-eslint/recommended',
    'react-hooks/recommended',
    'prettier'
  ],
  rules: {
    // React规则
    'react/jsx-no-useless-fragment': 'error',
    'react/no-unused-prop-types': 'error',
    'react/prefer-stateless-function': 'error',

    // TypeScript规则
    '@typescript-eslint/no-unused-vars': 'error',
    '@typescript-eslint/explicit-function-return-type': 'warn',
    '@typescript-eslint/prefer-interface': 'error',

    // 导入规则
    'import/order': ['error', {
      groups: [
        'builtin',
        'external',
        'internal',
        'parent',
        'sibling',
        'index'
      ],
      'newlines-between': 'always'
    }],

    // 自定义规则
    'no-console': 'warn',
    'prefer-const': 'error',
    'no-var': 'error'
  }
};

2. Git提交规范

# Conventional Commits规范
feat: 新功能
fix: 修复bug
docs: 文档更新
style: 代码格式调整
refactor: 重构
perf: 性能优化
test: 测试相关
chore: 构建工具或辅助工具的变动

# 示例
feat(user): 添加用户头像上传功能
fix(table): 修复表格排序异常问题
refactor(hooks): 重构useApi Hook的错误处理逻辑

最佳实践总结

1. 组件设计原则

  • 单一职责: 每个组件只负责一个功能
  • 高内聚: 相关功能聚合在一起
  • 低耦合: 组件间依赖关系最小化
  • 可测试: 组件易于单元测试
  • 可复用: 组件可在不同场景下复用

2. Hook设计原则

  • 功能聚合: 将相关的状态和逻辑组合在一起
  • 依赖明确: 明确声明依赖项
  • 副作用控制: 合理使用useEffect
  • 性能考虑: 使用useMemo和useCallback优化

3. 状态管理原则

  • 就近原则: 状态应该尽可能靠近使用它的组件
  • 单向数据流: 保持数据流的可预测性
  • 状态分层: 区分全局状态、页面状态、组件状态
  • 不可变性: 避免直接修改状态

4. 代码组织原则

  • 特性导向: 按功能模块组织代码
  • 层次清晰: 明确的抽象层次
  • 文件职责: 每个文件有明确的职责
  • 导出规范: 统一的导入导出方式

5. 开发流程

  1. 需求分析 → 明确功能需求和技术要求
  2. 架构设计 → 确定组件结构和数据流
  3. 接口设计 → 定义组件API和数据接口
  4. 实现开发 → 编写代码实现
  5. 测试验证 → 单元测试和集成测试
  6. 代码审查 → 团队代码Review
  7. 性能优化 → 性能分析和优化
  8. 文档完善 → 更新相关文档

6. 代码质量检查清单

  • 组件职责单一且明确
  • 类型定义完整准确
  • 错误处理完善
  • 性能优化到位
  • 测试覆盖充分
  • 代码风格一致
  • 注释清晰有效
  • 可访问性支持

工具推荐

开发工具

  • 编辑器: VS Code + React扩展
  • 类型检查: TypeScript
  • 代码格式: Prettier + ESLint
  • 测试: Jest + React Testing Library
  • 文档: Storybook
  • 调试: React DevTools

构建工具

  • 构建: Vite / Webpack
  • 包管理: pnpm / yarn
  • 代码分析: Bundle Analyzer
  • 性能监控: Lighthouse

质量保证

  • 提交检查: Husky + lint-staged
  • 类型检查: typescript
  • 单元测试: Jest
  • E2E测试: Playwright / Cypress

总结

好的前端代码应该具备以下特征:

  1. 可读性强 - 代码逻辑清晰,命名规范
  2. 可维护性高 - 结构合理,职责分明
  3. 可扩展性好 - 设计灵活,易于扩展
  4. 可测试性强 - 便于编写和执行测试
  5. 性能优秀 - 运行高效,用户体验好

遵循这些原则和实践,可以帮助团队构建高质量、可维护的前端应用。记住,好的架构是演进出来的,要根据项目的实际情况灵活调整和优化。