发布于
· 23 min read

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

Authors
  • avatar
    Name
    felixDu
    Twitter

项目架构设计原则

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. 性能优秀 - 运行高效,用户体验好

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