从基础到高级的系统化实战指南
React的核心在于组件化思维和单向数据流。理解这些基础概念是编写高质量React代码的前提。
// 推荐:使用函数组件 + Hooks
const UserCard = ({ user, onEdit }) => {
const [isEditing, setIsEditing] = useState(false);
const handleEdit = () => {
setIsEditing(!isEditing);
onEdit(user.id);
};
return (
<div className="user-card">
<h3>{user.name}</h3>
<button onClick={handleEdit}>
{isEditing ? '取消' : '编辑'}
</button>
</div>
);
};
// 避免:除非需要使用生命周期方法
class UserCard extends React.Component {
state = { isEditing: false };
handleEdit = () => {
this.setState(prevState => ({
isEditing: !prevState.isEditing
}));
};
render() {
const { user } = this.props;
const { isEditing } = this.state;
return (
<div className="user-card">
<h3>{user.name}</h3>
<button onClick={this.handleEdit}>
{isEditing ? '取消' : '编辑'}
</button>
</div>
);
}
}
// 好的Props设计
interface ButtonProps {
variant: 'primary' | 'secondary' | 'danger';
size: 'small' | 'medium' | 'large';
disabled?: boolean;
loading?: boolean;
onClick?: (event: React.MouseEvent) => void;
children: React.ReactNode;
className?: string;
}
// 组件实现
const Button: React.FC<ButtonProps> = ({
variant = 'primary',
size = 'medium',
disabled = false,
loading = false,
onClick,
children,
className = '',
...rest
}) => {
const baseClasses = 'btn';
const variantClasses = `btn-${variant}`;
const sizeClasses = `btn-${size}`;
const stateClasses = `${disabled ? 'disabled' : ''} ${loading ? 'loading' : ''}`;
return (
<button
className={`${baseClasses} ${variantClasses} ${sizeClasses} ${stateClasses} ${className}`}
disabled={disabled || loading}
onClick={onClick}
{...rest}
>
{loading ? <Spinner /> : children}
</button>
);
};
// 反模式:在render中创建新函数
const UserList = ({ users }) => {
return (
<div>
{users.map(user => (
<UserCard
key={user.id}
user={user}
onEdit={(id) => console.log('Edit user', id)} // 每次都创建新函数
/>
))}
</div>
);
};
// 最佳实践:使用useCallback缓存函数
const UserList = ({ users, onEditUser }) => {
const handleEdit = useCallback((id) => {
onEditUser(id);
}, [onEditUser]);
return (
<div>
{users.map(user => (
<UserCard
key={user.id}
user={user}
onEdit={handleEdit}
/>
))}
</div>
);
};
// 或者使用内联函数(如果依赖简单)
const UserList = ({ users }) => {
return (
<div>
{users.map(user => (
<UserCard
key={user.id}
user={user}
onEdit={() => console.log('Edit user', user.id)}
/>
))}
</div>
);
};
// 使用React.memo优化组件重渲染
const UserCard = React.memo(({ user, onEdit, onDelete }) => {
console.log('UserCard rendered:', user.name);
return (
<div className="user-card">
<h3>{user.name}</h3>
<p>{user.email}</p>
<div className="actions">
<button onClick={() => onEdit(user.id)}>编辑</button>
<button onClick={() => onDelete(user.id)}>删除</button>
</div>
</div>
);
}, (prevProps, nextProps) => {
// 自定义比较函数
return (
prevProps.user.id === nextProps.user.id &&
prevProps.user.name === nextProps.user.name &&
prevProps.user.email === nextProps.user.email &&
prevProps.onEdit === nextProps.onEdit &&
prevProps.onDelete === nextProps.onDelete
);
});
// 路由级别的代码分割
import { lazy, Suspense } from 'react';
const Dashboard = lazy(() => import('./pages/Dashboard'));
const UserManagement = lazy(() => import('./pages/UserManagement'));
const Settings = lazy(() => import('./pages/Settings'));
const App = () => {
return (
<Router>
<Suspense fallback={<LoadingSpinner />}>
<Routes>
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/users" element={<UserManagement />} />
<Route path="/settings" element={<Settings />} />
</Routes>
</Suspense>
</Router>
);
};
// 组件级别的懒加载
const LazyImage = lazy(() => import('./LazyImage'));
const ImageGallery = ({ images }) => {
return (
<div className="gallery">
{images.map(image => (
<Suspense key={image.id} fallback={<ImagePlaceholder />}>
<LazyImage src={image.url} alt={image.alt} />
</Suspense>
))}
</div>
);
};
// 使用React DevTools Profiler分析性能
import { Profiler } from 'react';
const onRenderCallback = (id, phase, actualDuration) => {
console.log('组件:', id);
console.log('阶段:', phase);
console.log('渲染时间:', actualDuration + 'ms');
if (actualDuration > 10) {
console.warn(`组件 ${id} 渲染时间过长:`, actualDuration + 'ms');
}
};
const App = () => {
return (
<Profiler id="App" onRender={onRenderCallback}>
<MainContent />
</Profiler>
);
};
// 自定义Hook:用户数据管理
const useUser = (userId) => {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const fetchUser = async () => {
try {
setLoading(true);
setError(null);
const userData = await api.getUser(userId);
setUser(userData);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
if (userId) {
fetchUser();
}
}, [userId]);
const updateUser = useCallback(async (updates) => {
try {
const updatedUser = await api.updateUser(userId, updates);
setUser(updatedUser);
return updatedUser;
} catch (err) {
setError(err.message);
throw err;
}
}, [userId]);
const deleteUser = useCallback(async () => {
try {
await api.deleteUser(userId);
setUser(null);
} catch (err) {
setError(err.message);
throw err;
}
}, [userId]);
return {
user,
loading,
error,
updateUser,
deleteUser,
refetch: () => setUser(prev => prev ? {...prev} : null)
};
};
// 使用自定义Hook
const UserProfile = ({ userId }) => {
const { user, loading, error, updateUser } = useUser(userId);
if (loading) return <LoadingSpinner />;
if (error) return <ErrorMessage message={error} />;
if (!user) return <NotFound />;
return (
<div className="user-profile">
<h2>{user.name}</h2>
<UserForm user={user} onSubmit={updateUser} />
</div>
);
};
// 反模式:无限循环
const DataComponent = ({ userId }) => {
const [data, setData] = useState([]);
// 错误:每次渲染都重新创建依赖数组
useEffect(() => {
fetchData(userId).then(setData);
}, [userId, fetchData]); // fetchData在useCallback中没有正确依赖
return <DataList data={data} />;
};
// 错误定义fetchData
const fetchData = (id) => {
return api.get(`/data/${id}`); // 每次都创建新函数
};
// 最佳实践:正确管理依赖
const DataComponent = ({ userId }) => {
const [data, setData] = useState([]);
const [loading, setLoading] = useState(false);
const fetchData = useCallback(async (id) => {
try {
setLoading(true);
const result = await api.get(`/data/${id}`);
setData(result);
} finally {
setLoading(false);
}
}, []); // 空依赖数组,因为函数内部没有使用外部变量
useEffect(() => {
if (userId) {
fetchData(userId);
}
}, [userId, fetchData]); // 正确依赖
// 或者使用useRef缓存函数
const fetchDataRef = useRef(fetchData);
fetchDataRef.current = fetchData;
useEffect(() => {
if (userId) {
fetchDataRef.current(userId);
}
}, [userId]); // 只需要userId依赖
return (
<div>
{loading ? <LoadingSpinner /> : <DataList data={data} />}
</div>
);
};
// ✅ 正确:使用"use"前缀
const useLocalStorage = (key, initialValue) => {
// hook实现
};
const useApi = (url) => {
// hook实现
};
const useDebounce = (value, delay) => {
// hook实现
};
const useWindowSize = () => {
// hook实现
};
// ❌ 错误:不使用"use"前缀
const localStorage = (key, initialValue) => {
// 这不是hook!
};
| 场景 | 推荐方案 | 原因 |
|---|---|---|
| 全局用户认证状态 | Context + useReducer | 需要全局访问,更新逻辑复杂 |
| 主题设置 | Context | 全局状态,更新简单 |
| 表单状态 | useState/useReducer | 组件局部状态 |
| 复杂列表状态 | useReducer + useMemo | 状态逻辑复杂但作用域有限 |
| 服务器状态 | SWR/React Query | 专门的缓存和同步机制 |
// 优化:拆分Context
const UserContext = React.createContext();
const ThemeContext = React.createContext();
// 用户状态Provider
const UserProvider = ({ children }) => {
const [user, setUser] = useState(null);
const value = useMemo(() => ({
user,
setUser,
login: (userData) => setUser(userData),
logout: () => setUser(null)
}), [user]);
return (
<UserContext.Provider value={value}>
{children}
</UserContext.Provider>
);
};
// 主题状态Provider
const ThemeProvider = ({ children }) => {
const [theme, setTheme] = useState('light');
const value = useMemo(() => ({
theme,
toggleTheme: () => setTheme(prev => prev === 'light' ? 'dark' : 'light')
}), []);
return (
<ThemeContext.Provider value={value}>
{children}
</ThemeContext.Provider>
);
};
// 使用优化后的Context
const Header = () => {
const { user, logout } = useContext(UserContext);
const { theme, toggleTheme } = useContext(ThemeContext);
// 只有当用户或主题变化时才重新渲染
return (
<header className={theme}>
{user && <WelcomeMessage user={user} />}
<button onClick={toggleTheme}>切换主题</button>
{user && <button onClick={logout}>退出</button>}
</header>
);
};
// Redux Toolkit - 适用于大型复杂应用
import { createSlice } from '@reduxjs/toolkit';
const userSlice = createSlice({
name: 'user',
initialState: {
currentUser: null,
users: [],
loading: false
},
reducers: {
setUser: (state, action) => {
state.currentUser = action.payload;
},
setUsers: (state, action) => {
state.users = action.payload;
},
setLoading: (state, action) => {
state.loading = action.payload;
}
}
});
// Zustand - 适用于中小型项目
import create from 'zustand';
const useStore = create((set, get) => ({
user: null,
setUser: (user) => set({ user }),
users: [],
setUsers: (users) => set({ users }),
fetchUser: async (id) => {
const user = await api.getUser(id);
set({ user });
}
}));
// Jotai - 适用于原子化状态管理
import { atom, useAtom } from 'jotai';
const userAtom = atom(null);
const loadingAtom = atom(false);
const UserComponent = () => {
const [user, setUser] = useAtom(userAtom);
const [loading, setLoading] = useAtom(loadingAtom);
// 使用逻辑
};
src/
├── components/ # 通用组件
│ ├── ui/ # UI基础组件
│ │ ├── Button/
│ │ ├── Modal/
│ │ └── Input/
│ ├── layout/ # 布局组件
│ │ ├── Header/
│ │ ├── Footer/
│ │ └── Sidebar/
│ └── common/ # 业务通用组件
│ ├── UserAvatar/
│ ├── DataTable/
│ └── SearchBox/
├── features/ # 功能模块
│ ├── auth/ # 认证模块
│ │ ├── components/
│ │ ├── hooks/
│ │ ├── utils/
│ │ ├── types.ts
│ │ └── index.ts
│ ├── dashboard/ # 仪表板模块
│ └── users/ # 用户管理模块
├── hooks/ # 全局自定义Hooks
├── utils/ # 工具函数
├── types/ # TypeScript类型定义
├── constants/ # 常量定义
└── styles/ # 样式文件
// components/ui/Button/index.tsx
import React from 'react';
import './Button.css';
export interface ButtonProps {
variant?: 'primary' | 'secondary' | 'danger';
size?: 'small' | 'medium' | 'large';
disabled?: boolean;
loading?: boolean;
children: React.ReactNode;
onClick?: (e: React.MouseEvent) => void;
className?: string;
type?: 'button' | 'submit' | 'reset';
}
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({
variant = 'primary',
size = 'medium',
disabled = false,
loading = false,
children,
onClick,
className = '',
type = 'button',
...props
}, ref) => {
const handleClick = (e: React.MouseEvent) => {
if (!disabled && !loading && onClick) {
onClick(e);
}
};
return (
<button
ref={ref}
type={type}
className={`btn btn--${variant} btn--${size} ${className}`}
disabled={disabled || loading}
onClick={handleClick}
{...props}
>
{loading && <span className="btn__spinner" />}
<span className="btn__content">{children}</span>
</button>
);
}
);
Button.displayName = 'Button';
export default Button;
// features/users/components/UserList/UserList.tsx
import React, { memo } from 'react';
import { User } from '../../types';
import UserCard from '../UserCard';
import { useUsers } from '../../hooks/useUsers';
interface UserListProps {
searchTerm?: string;
onUserSelect?: (user: User) => void;
}
const UserList: React.FC<UserListProps> = memo(({ searchTerm = '', onUserSelect }) => {
const { users, loading, error } = useUsers();
const filteredUsers = useMemo(() => {
if (!searchTerm) return users;
return users.filter(user =>
user.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
user.email.toLowerCase().includes(searchTerm.toLowerCase())
);
}, [users, searchTerm]);
if (loading) return <UserListSkeleton />;
if (error) return <ErrorMessage message={error} />;
return (
<div className="user-list">
{filteredUsers.map(user => (
<UserCard
key={user.id}
user={user}
onClick={() => onUserSelect?.(user)}
/>
))}
</div>
);
});
UserList.displayName = 'UserList';
export default UserList;
// types/common.ts - 通用类型定义
export interface BaseEntity {
id: string;
createdAt: Date;
updatedAt: Date;
}
export interface User extends BaseEntity {
name: string;
email: string;
avatar?: string;
role: 'admin' | 'user' | 'moderator';
isActive: boolean;
}
export interface ApiResponse<T> {
data: T;
message?: string;
success: boolean;
error?: string;
}
// types/features/users.ts - 功能相关类型
export interface UserFilters {
search?: string;
role?: User['role'];
isActive?: boolean;
sortBy?: 'name' | 'email' | 'createdAt';
sortOrder?: 'asc' | 'desc';
}
export interface UserFormData {
name: string;
email: string;
role: User['role'];
}
// 泛型组件示例
interface TableProps<T> {
data: T[];
columns: ColumnDef<T>[];
onRowClick?: (row: T) => void;
loading?: boolean;
}
interface ColumnDef<T> {
key: keyof T;
title: string;
render?: (value: T[keyof T], row: T) => React.ReactNode;
sortable?: boolean;
}
const Table = <T extends Record<string, any>>({
data,
columns,
onRowClick,
loading = false
}: TableProps<T>) => {
// 组件实现
};
// components/Button/Button.test.tsx
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import Button from './Button';
describe('Button', () => {
it('应该正确渲染按钮文本', () => {
render(<Button>点击我</Button>);
expect(screen.getByRole('button', { name: '点击我' })).toBeInTheDocument();
});
it('应该在点击时调用onClick回调', async () => {
const handleClick = jest.fn();
const user = userEvent.setup();
render(<Button onClick={handleClick}>点击我</Button>);
await user.click(screen.getByRole('button'));
expect(handleClick).toHaveBeenCalledTimes(1);
});
it('应该在loading时显示加载状态并禁用按钮', () => {
render(<Button loading>加载中</Button>);
const button = screen.getByRole('button');
expect(button).toBeDisabled();
expect(screen.getByText('加载中')).toBeInTheDocument();
});
it('应该支持不同的变体样式', () => {
const { rerender } = render(<Button variant="primary">主要按钮</Button>);
let button = screen.getByRole('button');
expect(button).toHaveClass('btn--primary');
rerender(<Button variant="secondary">次要按钮</Button>);
button = screen.getByRole('button');
expect(button).toHaveClass('btn--secondary');
});
});
// features/users/components/UserList/UserList.test.tsx
import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import UserList from './UserList';
import { useUsers } from '../../hooks/useUsers';
// Mock自定义Hook
jest.mock('../../hooks/useUsers');
const mockUseUsers = useUsers as jest.MockedFunction<typeof useUsers>;
describe('UserList', () => {
const mockUsers = [
{ id: '1', name: '张三', email: '[email protected]', role: 'user', isActive: true },
{ id: '2', name: '李四', email: '[email protected]', role: 'admin', isActive: false }
];
beforeEach(() => {
mockUseUsers.mockReturnValue({
users: mockUsers,
loading: false,
error: null,
refetch: jest.fn()
});
});
it('应该渲染用户列表', async () => {
render(<UserList />);
await waitFor(() => {
expect(screen.getByText('张三')).toBeInTheDocument();
expect(screen.getByText('李四')).toBeInTheDocument();
});
});
it('应该根据搜索词过滤用户', async () => {
const user = userEvent.setup();
render(<UserList />);
const searchInput = screen.getByPlaceholderText('搜索用户...');
await user.type(searchInput, '张三');
await waitFor(() => {
expect(screen.getByText('张三')).toBeInTheDocument();
expect(screen.queryByText('李四')).not.toBeInTheDocument();
});
});
it('应该在用户为空时显示空状态', () => {
mockUseUsers.mockReturnValue({
users: [],
loading: false,
error: null,
refetch: jest.fn()
});
render(<UserList />);
expect(screen.getByText('暂无用户数据')).toBeInTheDocument();
});
});
// hooks/useUser/useUser.test.ts
import { renderHook, waitFor } from '@testing-library/react';
import { act } from 'react-dom/test-utils';
import { useUser } from './useUser';
import * as api from '../../api/users';
// Mock API
jest.mock('../../api/users');
const mockApi = api as jest.Mocked<typeof api>;
describe('useUser', () => {
beforeEach(() => {
jest.clearAllMocks();
});
it('应该初始加载用户数据', async () => {
const mockUser = { id: '1', name: '张三', email: '[email protected]' };
mockApi.getUser.mockResolvedValue(mockUser);
const { result } = renderHook(() => useUser('1'));
expect(result.current.loading).toBe(true);
await waitFor(() => {
expect(result.current.loading).toBe(false);
});
expect(result.current.user).toEqual(mockUser);
expect(result.current.error).toBeNull();
});
it('应该在API调用失败时设置错误状态', async () => {
const mockError = new Error('用户不存在');
mockApi.getUser.mockRejectedValue(mockError);
const { result } = renderHook(() => useUser('999'));
expect(result.current.loading).toBe(true);
await waitFor(() => {
expect(result.current.loading).toBe(false);
});
expect(result.current.user).toBeNull();
expect(result.current.error).toBe('用户不存在');
});
it('应该正确更新用户信息', async () => {
const initialUser = { id: '1', name: '张三', email: '[email protected]' };
const updatedUser = { id: '1', name: '张三丰', email: '[email protected]' };
mockApi.getUser.mockResolvedValue(initialUser);
mockApi.updateUser.mockResolvedValue(updatedUser);
const { result } = renderHook(() => useUser('1'));
// 等待初始加载完成
await waitFor(() => {
expect(result.current.loading).toBe(false);
});
// 执行更新操作
await act(async () => {
await result.current.updateUser({ name: '张三丰' });
});
expect(mockApi.updateUser).toHaveBeenCalledWith('1', { name: '张三丰' });
expect(result.current.user).toEqual(updatedUser);
});
});
// vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { resolve } from 'path';
export default defineConfig({
plugins: [react()],
resolve: {
alias: {
'@': resolve(__dirname, 'src'),
'@/components': resolve(__dirname, 'src/components'),
'@/features': resolve(__dirname, 'src/features'),
'@/hooks': resolve(__dirname, 'src/hooks'),
'@/utils': resolve(__dirname, 'src/utils')
}
},
build: {
rollupOptions: {
output: {
manualChunks: {
vendor: ['react', 'react-dom'],
ui: ['@mui/material', '@emotion/react'],
utils: ['lodash', 'date-fns']
}
}
},
chunkSizeWarningLimit: 1000,
sourcemap: true
},
server: {
port: 3000,
open: true,
host: true
},
preview: {
port: 4173
}
});
// .eslintrc.json
{
"extends": [
"react-app",
"react-app/jest",
"@typescript-eslint/recommended",
"prettier"
],
"rules": {
"react-hooks/exhaustive-deps": "warn",
"@typescript-eslint/no-unused-vars": "error",
"prefer-const": "error",
"no-var": "error",
"react-hooks/rules-of-hooks": "error"
},
"settings": {
"react": {
"version": "detect"
}
}
}
// package.json scripts
{
"scripts": {
"start": "vite",
"build": "tsc && vite build",
"preview": "vite preview",
"lint": "eslint src --ext .ts,.tsx --report-unused-disable-directives --max-warnings 0",
"lint:fix": "eslint src --ext .ts,.tsx --fix",
"type-check": "tsc --noEmit",
"test": "jest",
"test:watch": "jest --watch",
"test:coverage": "jest --coverage",
"prepare": "husky install",
"format": "prettier --write \"src/**/*.{ts,tsx,json,css,md}\"",
"check": "npm run type-check && npm run lint && npm run test"
}
}
#!/bin/sh
# .husky/pre-commit
. "$(dirname "$0")/_/husky.sh"
# 运行类型检查
npm run type-check
# 运行 lint 检查
npm run lint
# 如果检查失败,则阻止提交
if [ $? -ne 0 ]; then
echo "❌ 提交被阻止:代码质量检查失败"
exit 1
fi
echo "✅ 代码质量检查通过,允许提交"
// jest.config.js
module.exports = {
testEnvironment: 'jsdom',
setupFilesAfterEnv: ['<rootDir>/src/setupTests.ts'],
moduleNameMapping: {
'^@/(.*)$': '<rootDir>/src/$1',
'\\.(css|less|scss|sass)$': 'identity-obj-proxy'
},
collectCoverageFrom: [
'src/**/*.{ts,tsx}',
'!src/**/*.d.ts',
'!src/**/*.stories.{ts,tsx}',
'!src/index.tsx'
],
coverageThreshold: {
global: {
branches: 80,
functions: 80,
lines: 80,
statements: 80
}
},
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],
transform: {
'^.+\\.(ts|tsx)$': 'ts-jest'
}
};
// 反模式:useEffect滥用
const UserProfile = ({ userId }) => {
const [user, setUser] = useState(null);
const [posts, setPosts] = useState([]);
const [loading, setLoading] = useState(false);
// 太多useEffect,难以追踪依赖
useEffect(() => {
if (userId) {
api.getUser(userId).then(setUser);
}
}, [userId]);
useEffect(() => {
if (user) {
api.getUserPosts(user.id).then(setPosts);
}
}, [user]);
useEffect(() => {
if (user && posts.length > 0) {
localStorage.setItem('user-posts', JSON.stringify(posts));
}
}, [user, posts]);
useEffect(() => {
document.title = user ? `${user.name}的资料` : '用户资料';
}, [user]);
return (
<div>
{loading ? <Loading /> : (
<div>
<h1>{user?.name}</h1>
<PostsList posts={posts} />
</div>
)}
</div>
);
};
// 自定义Hook:用户数据管理
const useUserProfile = (userId) => {
const [user, setUser] = useState(null);
const [posts, setPosts] = useState([]);
const [loading, setLoading] = useState(false);
// 使用useCallback缓存函数
const fetchUser = useCallback(async (id) => {
setLoading(true);
try {
const userData = await api.getUser(id);
setUser(userData);
return userData;
} finally {
setLoading(false);
}
}, []);
const fetchUserPosts = useCallback(async (userId) => {
const postsData = await api.getUserPosts(userId);
setPosts(postsData);
return postsData;
}, []);
// 链式调用,减少嵌套
useEffect(() => {
if (userId) {
fetchUser(userId).then(user => {
if (user) {
fetchUserPosts(user.id);
}
});
}
}, [userId, fetchUser, fetchUserPosts]);
// 副作用分离到独立的effect
useEffect(() => {
if (posts.length > 0) {
localStorage.setItem('user-posts', JSON.stringify(posts));
}
}, [posts]);
return {
user,
posts,
loading,
refetch: () => {
if (userId) {
fetchUser(userId).then(user => {
if (user) {
fetchUserPosts(user.id);
}
});
}
}
};
};
// 自定义Hook:页面标题管理
const useDocumentTitle = (title) => {
useEffect(() => {
const originalTitle = document.title;
document.title = title || originalTitle;
return () => {
document.title = originalTitle;
};
}, [title]);
};
// 重构后的组件:职责分离
const UserProfile = ({ userId }) => {
const { user, posts, loading } = useUserProfile(userId);
useDocumentTitle(user ? `${user.name}的资料` : '用户资料');
if (loading) return <Loading />;
if (!user) return <NotFound />;
return (
<div>
<h1>{user.name}</h1>
<UserInfo user={user} />
<PostsList posts={posts} />
</div>
);
};
症状:1000+条数据的列表滚动卡顿
原因:所有列表项同时渲染
// 使用react-window
import { FixedSizeList as List } from 'react-window';
const VirtualizedUserList = ({ users }) => {
const Row = ({ index, style }) => (
<div style={style}>
<UserCard user={users[index]} />
</div>
);
return (
<List
height={600} // 容器高度
itemCount={users.length}
itemSize={80} // 每项高度
width="100%"
>
{Row}
</List>
);
};
// 重构前:300行的巨无霸组件
const ComplexForm = () => {
const [formData, setFormData] = useState({});
const [errors, setErrors] = useState({});
const [loading, setLoading] = useState(false);
const [validationRules, setValidationRules] = useState({});
const [fieldConfigs, setFieldConfigs] = useState({});
const [userPermissions, setUserPermissions] = useState({});
const [draftData, setDraftData] = useState({});
// ... 20+ 个 state
// 50+ 个函数处理各种逻辑
const handleFieldChange = () => { /* ... */ };
const validateField = () => { /* ... */ };
const validateForm = () => { /* ... */ };
const submitForm = async () => { /* ... */ };
const saveDraft = () => { /* ... */ };
// ... 更多函数
// 200+ 行 render 逻辑
return (
<form>
{/* 表单字段 */}
{/* 验证错误显示 */}
{/* 权限控制 */}
{/* 自动保存 */}
{/* 等等... */}
</form>
);
};
// 重构后:模块化组件结构
// 1. 自定义Hook:表单状态管理
const useForm = (initialData, validationRules) => {
const [data, setData] = useState(initialData);
const [errors, setErrors] = useState({});
const [isDirty, setIsDirty] = useState(false);
const validateField = useCallback((name, value) => {
const rule = validationRules[name];
if (!rule) return null;
return rule(value, data);
}, [validationRules, data]);
const setFieldValue = useCallback((name, value) => {
setData(prev => ({ ...prev, [name]: value }));
setIsDirty(true);
const error = validateField(name, value);
setErrors(prev => ({ ...prev, [name]: error }));
}, [validateField]);
const validateForm = useCallback(() => {
const newErrors = {};
Object.keys(validationRules).forEach(field => {
const error = validateField(field, data[field]);
if (error) newErrors[field] = error;
});
setErrors(newErrors);
return Object.keys(newErrors).length === 0;
}, [data, validationRules, validateField]);
return {
data,
errors,
isDirty,
setFieldValue,
validateForm,
setErrors: setErrors
};
};
// 2. 权限控制Hook
const usePermissions = () => {
const { user } = useAuth();
const [permissions, setPermissions] = useState({});
useEffect(() => {
if (user) {
fetchUserPermissions(user.role).then(setPermissions);
}
}, [user]);
const canEdit = useCallback((field) => {
return permissions[field]?.canEdit ?? true;
}, [permissions]);
return { canEdit, permissions };
};
// 3. 自动保存Hook
const useAutoSave = (data, interval = 30000) => {
const [lastSaved, setLastSaved] = useState(null);
useEffect(() => {
const timer = setInterval(() => {
if (Object.keys(data).length > 0) {
localStorage.setItem('form-draft', JSON.stringify(data));
setLastSaved(new Date());
}
}, interval);
return () => clearInterval(timer);
}, [data, interval]);
return { lastSaved };
};
// 4. 主组件:职责清晰
const ComplexForm = () => {
const { user } = useAuth();
const { canEdit } = usePermissions();
const { data, errors, setFieldValue, validateForm } = useForm(
initialFormData,
validationRules
);
const { lastSaved } = useAutoSave(data);
const handleSubmit = async (formData) => {
if (validateForm()) {
await submitForm(formData);
}
};
return (
<Form onSubmit={handleSubmit}>
<FormFields
data={data}
errors={errors}
onFieldChange={setFieldValue}
canEdit={canEdit}
/>
<FormActions />
<AutoSaveIndicator lastSaved={lastSaved} />
</Form>
);
};