- Published on
- · 24 min read
前端代码设计最佳实践指南
- Authors
- Name
- felixDu
Table of Contents
前端代码设计最佳实践指南
目录
项目架构设计原则
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. 开发流程
- 需求分析 → 明确功能需求和技术要求
- 架构设计 → 确定组件结构和数据流
- 接口设计 → 定义组件API和数据接口
- 实现开发 → 编写代码实现
- 测试验证 → 单元测试和集成测试
- 代码审查 → 团队代码Review
- 性能优化 → 性能分析和优化
- 文档完善 → 更新相关文档
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
总结
好的前端代码应该具备以下特征:
- 可读性强 - 代码逻辑清晰,命名规范
- 可维护性高 - 结构合理,职责分明
- 可扩展性好 - 设计灵活,易于扩展
- 可测试性强 - 便于编写和执行测试
- 性能优秀 - 运行高效,用户体验好
遵循这些原则和实践,可以帮助团队构建高质量、可维护的前端应用。记住,好的架构是演进出来的,要根据项目的实际情况灵活调整和优化。