React全家桶实战:Redux+Antd+Hooks构建企业级前端应用
本文深入探讨React生态核心技术的整合应用,通过完整的企业级项目实战,展示Redux状态管理、Antd组件库、React Hooks的协同开发模式,提供可直接落地的架构方案与性能优化策略。
一、技术选型与架构设计
现代React应用开发已形成稳定的技术栈组合。本文选取以下核心依赖:
json
perl
{
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-redux": "^8.0.5",
"@reduxjs/toolkit": "^1.9.3",
"antd": "^5.1.0",
"axios": "^1.3.0",
"react-router-dom": "^6.8.0"
}
架构分层设计采用Ducks模式 整合Redux代码,配合Hooks封装实现逻辑复用,Antd作为UI基础组件库。
text
bash
src/
├── api/ # 接口层
├── store/ # Redux状态管理
│ ├── slices/ # RTK切片
│ └── index.ts # Store配置
├── hooks/ # 自定义Hooks
├── components/ # 公共组件
├── pages/ # 页面组件
└── utils/ # 工具函数
二、Redux Toolkit状态管理实践
2.1 Store配置与切片设计
使用Redux Toolkit简化Redux配置,通过createSlice自动生成action creators:
typescript
typescript
// store/slices/userSlice.ts
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
import { userApi } from '@/api/user';
export interface UserState {
profile: UserProfile | null;
loading: boolean;
error: string | null;
}
const initialState: UserState = {
profile: null,
loading: false,
error: null
};
export const fetchUserProfile = createAsyncThunk(
'user/fetchProfile',
async (userId: string, { rejectWithValue }) => {
try {
const response = await userApi.getProfile(userId);
return response.data;
} catch (error) {
return rejectWithValue(error.message);
}
}
);
const userSlice = createSlice({
name: 'user',
initialState,
reducers: {
clearUser: (state) => {
state.profile = null;
state.error = null;
},
updateUser: (state, action) => {
state.profile = { ...state.profile, ...action.payload };
}
},
extraReducers: (builder) => {
builder
.addCase(fetchUserProfile.pending, (state) => {
state.loading = true;
state.error = null;
})
.addCase(fetchUserProfile.fulfilled, (state, action) => {
state.loading = false;
state.profile = action.payload;
})
.addCase(fetchUserProfile.rejected, (state, action) => {
state.loading = false;
state.error = action.payload as string;
});
}
});
export const { clearUser, updateUser } = userSlice.actions;
export default userSlice.reducer;
2.2 Store组合与类型定义
typescript
typescript
// store/index.ts
import { configureStore } from '@reduxjs/toolkit';
import userReducer from './slices/userSlice';
import appReducer from './slices/appSlice';
export const store = configureStore({
reducer: {
user: userReducer,
app: appReducer
},
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({
serializableCheck: false,
thunk: true
}),
devTools: process.env.NODE_ENV !== 'production'
});
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
2.3 类型化Hooks封装
typescript
typescript
// hooks/redux.ts
import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux';
import type { RootState, AppDispatch } from '@/store';
export const useAppDispatch = () => useDispatch<AppDispatch>();
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
三、Antd组件系统集成
3.1 主题定制与全局配置
Antd 5.x支持CSS-in-JS主题动态切换:
typescript
javascript
// App.tsx
import { ConfigProvider, theme, App as AntApp } from 'antd';
import { useSelector } from 'react-redux';
import { RootState } from '@/store';
const App: React.FC = () => {
const themeMode = useSelector((state: RootState) => state.app.theme);
return (
<ConfigProvider
theme={{
algorithm: themeMode === 'dark'
? theme.darkAlgorithm
: theme.defaultAlgorithm,
token: {
colorPrimary: '#1890ff',
borderRadius: 8,
fontSize: 14
},
components: {
Table: {
headerBg: '#fafafa',
rowHoverBg: '#e6f7ff'
}
}
}}
>
<AntApp>
<RouterProvider router={router} />
</AntApp>
</ConfigProvider>
);
};
3.2 高阶组件封装
封装可复用的表格组件,集成分页、筛选功能:
tsx
typescript
// components/DataTable/index.tsx
import { Table, Space, Button, Input, Form, Modal } from 'antd';
import { SearchOutlined, PlusOutlined } from '@ant-design/icons';
import { useAntdTable } from '@/hooks/useAntdTable';
interface DataTableProps<T> {
columns: ColumnsType<T>;
fetchData: (params: any) => Promise<{ list: T[]; total: number }>;
searchFields?: SearchField[];
onCreate?: () => void;
}
export function DataTable<T extends object>({
columns,
fetchData,
searchFields,
onCreate
}: DataTableProps<T>) {
const [form] = Form.useForm();
const { data, loading, pagination, search, refresh } = useAntdTable({
fetchData,
form,
defaultPageSize: 20
});
return (
<div className="data-table-container">
<div className="table-header">
<Form form={form} layout="inline" onFinish={search}>
{searchFields?.map(field => (
<Form.Item key={field.name} name={field.name}>
{field.type === 'input' && (
<Input placeholder={field.placeholder} allowClear />
)}
{field.type === 'select' && (
<Select options={field.options} placeholder={field.placeholder} />
)}
</Form.Item>
))}
<Form.Item>
<Button type="primary" htmlType="submit" icon={<SearchOutlined />}>
查询
</Button>
<Button onClick={() => form.resetFields()} style={{ marginLeft: 8 }}>
重置
</Button>
</Form.Item>
</Form>
{onCreate && (
<Button type="primary" icon={<PlusOutlined />} onClick={onCreate}>
新增
</Button>
)}
</div>
<Table
columns={columns}
dataSource={data}
loading={loading}
pagination={{
...pagination,
showSizeChanger: true,
showQuickJumper: true,
showTotal: (total) => `共 ${total} 条`
}}
rowKey="id"
scroll={{ x: 'max-content' }}
/>
</div>
);
}
四、React Hooks深度应用
4.1 自定义数据请求Hook
typescript
typescript
// hooks/useRequest.ts
import { useState, useEffect, useCallback, useRef } from 'react';
import { message } from 'antd';
interface UseRequestOptions<T> {
manual?: boolean;
onSuccess?: (data: T) => void;
onError?: (error: Error) => void;
debounce?: number;
retryCount?: number;
}
export function useRequest<T, P extends any[] = any[]>(
service: (...args: P) => Promise<T>,
options: UseRequestOptions<T> = {}
) {
const { manual = false, onSuccess, onError, debounce, retryCount = 0 } = options;
const [data, setData] = useState<T | null>(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<Error | null>(null);
const timerRef = useRef<NodeJS.Timeout | null>(null);
const retryRef = useRef(0);
const run = useCallback(
async (...args: P) => {
if (debounce) {
return new Promise<T>((resolve, reject) => {
if (timerRef.current) clearTimeout(timerRef.current);
timerRef.current = setTimeout(async () => {
try {
const result = await executeRequest(...args);
resolve(result);
} catch (err) {
reject(err);
}
}, debounce);
});
}
return executeRequest(...args);
},
[service, debounce]
);
const executeRequest = async (...args: P): Promise<T> => {
setLoading(true);
setError(null);
try {
const result = await service(...args);
setData(result);
setLoading(false);
onSuccess?.(result);
retryRef.current = 0;
return result;
} catch (err) {
const error = err instanceof Error ? err : new Error(String(err));
setError(error);
setLoading(false);
onError?.(error);
if (retryRef.current < retryCount) {
retryRef.current++;
return executeRequest(...args);
}
message.error(error.message || '请求失败');
throw error;
}
};
useEffect(() => {
if (!manual) {
run(...([] as unknown as P));
}
return () => {
if (timerRef.current) clearTimeout(timerRef.current);
};
}, [manual]);
return { data, loading, error, run, refresh: () => run(...([] as unknown as P)) };
}
4.2 防抖搜索Hook
tsx
typescript
// hooks/useDebounceSearch.ts
import { useState, useEffect, useCallback } from 'react';
import { useAppDispatch, useAppSelector } from './redux';
export function useDebounceSearch<T>(
searchAction: (params: any) => any,
debounceDelay: number = 300
) {
const [keyword, setKeyword] = useState('');
const [debouncedKeyword, setDebouncedKeyword] = useState('');
const dispatch = useAppDispatch();
const data = useAppSelector((state) => state.search.data);
const loading = useAppSelector((state) => state.search.loading);
useEffect(() => {
const timer = setTimeout(() => {
setDebouncedKeyword(keyword);
}, debounceDelay);
return () => clearTimeout(timer);
}, [keyword, debounceDelay]);
useEffect(() => {
if (debouncedKeyword !== undefined) {
dispatch(searchAction({ keyword: debouncedKeyword }));
}
}, [debouncedKeyword, dispatch, searchAction]);
const handleSearch = useCallback((value: string) => {
setKeyword(value);
}, []);
return { data, loading, keyword, handleSearch };
}
4.3 响应式断点Hook
typescript
ini
// hooks/useResponsive.ts
import { useState, useEffect } from 'react';
type Breakpoint = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'xxl';
const breakpoints: Record<Breakpoint, number> = {
xs: 480,
sm: 576,
md: 768,
lg: 992,
xl: 1200,
xxl: 1600
};
export function useResponsive() {
const [currentBreakpoint, setCurrentBreakpoint] = useState<Breakpoint>('lg');
useEffect(() => {
const handleResize = () => {
const width = window.innerWidth;
let bp: Breakpoint = 'xs';
for (const [key, value] of Object.entries(breakpoints)) {
if (width >= value) {
bp = key as Breakpoint;
}
}
setCurrentBreakpoint(bp);
};
handleResize();
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
const isMobile = currentBreakpoint === 'xs' || currentBreakpoint === 'sm';
const isTablet = currentBreakpoint === 'md' || currentBreakpoint === 'lg';
const isDesktop = currentBreakpoint === 'xl' || currentBreakpoint === 'xxl';
return { currentBreakpoint, isMobile, isTablet, isDesktop };
}
五、数据请求与状态管理整合
5.1 Axios封装与拦截器
typescript
typescript
// utils/request.ts
import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios';
import { message } from 'antd';
import { store } from '@/store';
import { logout } from '@/store/slices/userSlice';
class Request {
private instance: AxiosInstance;
constructor(baseURL: string) {
this.instance = axios.create({
baseURL,
timeout: 30000,
headers: {
'Content-Type': 'application/json'
}
});
this.setupInterceptors();
}
private setupInterceptors() {
this.instance.interceptors.request.use(
(config) => {
const token = localStorage.getItem('accessToken');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
(error) => Promise.reject(error)
);
this.instance.interceptors.response.use(
(response: AxiosResponse) => {
const { code, data, message: msg } = response.data;
if (code === 200) {
return data;
}
message.error(msg || '请求失败');
return Promise.reject(new Error(msg));
},
(error) => {
if (error.response) {
const { status } = error.response;
if (status === 401) {
store.dispatch(logout());
message.error('登录已过期,请重新登录');
window.location.href = '/login';
} else if (status === 403) {
message.error('无权限访问');
} else if (status === 500) {
message.error('服务器内部错误');
} else {
message.error(error.response.data?.message || '请求失败');
}
} else if (error.code === 'ECONNABORTED') {
message.error('请求超时');
} else {
message.error('网络异常');
}
return Promise.reject(error);
}
);
}
public get<T = any>(url: string, config?: AxiosRequestConfig): Promise<T> {
return this.instance.get(url, config);
}
public post<T = any>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T> {
return this.instance.post(url, data, config);
}
public put<T = any>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T> {
return this.instance.put(url, data, config);
}
public delete<T = any>(url: string, config?: AxiosRequestConfig): Promise<T> {
return this.instance.delete(url, config);
}
}
export const request = new Request(process.env.REACT_APP_API_URL || '/api');
5.2 业务页面完整示例
tsx
typescript
// pages/UserManagement/index.tsx
import React, { useEffect } from 'react';
import { Card, Space, Tag, Button, Modal, Form, Input, Select } from 'antd';
import { EditOutlined, DeleteOutlined, EyeOutlined } from '@ant-design/icons';
import { useAppDispatch, useAppSelector } from '@/hooks/redux';
import { useRequest } from '@/hooks/useRequest';
import { DataTable } from '@/components/DataTable';
import { fetchUsers, deleteUser, updateUser } from '@/store/slices/userSlice';
const UserManagement: React.FC = () => {
const dispatch = useAppDispatch();
const { users, loading, total } = useAppSelector((state) => state.user);
const [form] = Form.useForm();
const [modalVisible, setModalVisible] = useState(false);
const [editingUser, setEditingUser] = useState<User | null>(null);
const { run: handleDelete } = useRequest(
async (id: string) => {
await dispatch(deleteUser(id)).unwrap();
message.success('删除成功');
refreshTable();
},
{ manual: true }
);
const columns: ColumnsType<User> = [
{
title: '用户名',
dataIndex: 'username',
key: 'username',
render: (text) => <span className="font-medium">{text}</span>
},
{
title: '角色',
dataIndex: 'role',
key: 'role',
render: (role) => (
<Tag color={role === 'admin' ? 'red' : 'blue'}>
{role === 'admin' ? '管理员' : '普通用户'}
</Tag>
)
},
{
title: '状态',
dataIndex: 'status',
key: 'status',
render: (status) => (
<Tag color={status === 'active' ? 'green' : 'gray'}>
{status === 'active' ? '活跃' : '禁用'}
</Tag>
)
},
{
title: '操作',
key: 'action',
render: (_, record) => (
<Space>
<Button type="link" icon={<EyeOutlined />} onClick={() => handleView(record)}>
查看
</Button>
<Button type="link" icon={<EditOutlined />} onClick={() => handleEdit(record)}>
编辑
</Button>
<Button
type="link"
danger
icon={<DeleteOutlined />}
onClick={() => handleDelete(record.id)}
>
删除
</Button>
</Space>
)
}
];
const fetchData = async (params: any) => {
const result = await dispatch(fetchUsers(params)).unwrap();
return { list: result.list, total: result.total };
};
const handleEdit = (user: User) => {
setEditingUser(user);
form.setFieldsValue(user);
setModalVisible(true);
};
const handleSubmit = async () => {
const values = await form.validateFields();
await dispatch(updateUser({ id: editingUser!.id, ...values })).unwrap();
message.success('更新成功');
setModalVisible(false);
refreshTable();
};
return (
<Card title="用户管理" className="user-management">
<DataTable<User>
columns={columns}
fetchData={fetchData}
searchFields={[
{ name: 'username', type: 'input', placeholder: '请输入用户名' },
{
name: 'role',
type: 'select',
placeholder: '请选择角色',
options: [
{ label: '管理员', value: 'admin' },
{ label: '普通用户', value: 'user' }
]
}
]}
/>
<Modal
title="编辑用户"
open={modalVisible}
onOk={handleSubmit}
onCancel={() => setModalVisible(false)}
width={600}
>
<Form form={form} layout="vertical">
<Form.Item
name="username"
label="用户名"
rules={[{ required: true, message: '请输入用户名' }]}
>
<Input />
</Form.Item>
<Form.Item
name="email"
label="邮箱"
rules={[{ type: 'email', message: '请输入有效邮箱' }]}
>
<Input />
</Form.Item>
<Form.Item name="role" label="角色">
<Select>
<Select.Option value="admin">管理员</Select.Option>
<Select.Option value="user">普通用户</Select.Option>
</Select>
</Form.Item>
</Form>
</Modal>
</Card>
);
};
export default UserManagement;
六、性能优化策略
6.1 组件懒加载
tsx
javascript
// router/index.tsx
import { lazy, Suspense } from 'react';
import { Spin } from 'antd';
const Dashboard = lazy(() => import('@/pages/Dashboard'));
const UserManagement = lazy(() => import('@/pages/UserManagement'));
const Settings = lazy(() => import('@/pages/Settings'));
const withSuspense = (Component: React.LazyExoticComponent<React.ComponentType>) => (
<Suspense fallback={<Spin size="large" className="global-spin" />}>
<Component />
</Suspense>
);
export const routes = [
{
path: '/dashboard',
element: withSuspense(Dashboard)
},
{
path: '/users',
element: withSuspense(UserManagement)
},
{
path: '/settings',
element: withSuspense(Settings)
}
];
6.2 memo与useCallback优化
tsx
typescript
// components/UserCard/index.tsx
import React, { memo, useCallback } from 'react';
import { Card, Avatar, Button } from 'antd';
interface UserCardProps {
user: User;
onEdit: (id: string) => void;
onDelete: (id: string) => void;
}
const UserCard = memo(({ user, onEdit, onDelete }: UserCardProps) => {
const handleEdit = useCallback(() => {
onEdit(user.id);
}, [onEdit, user.id]);
const handleDelete = useCallback(() => {
onDelete(user.id);
}, [onDelete, user.id]);
return (
<Card>
<Card.Meta
avatar={<Avatar src={user.avatar} />}
title={user.username}
description={user.email}
/>
<Space>
<Button onClick={handleEdit}>编辑</Button>
<Button danger onClick={handleDelete}>删除</Button>
</Space>
</Card>
);
});
UserCard.displayName = 'UserCard';
export default UserCard;
6.3 选择性Redux订阅
typescript
javascript
// hooks/useUserProfile.ts
import { useAppSelector } from './redux';
import { createSelector } from '@reduxjs/toolkit';
const selectUserProfile = createSelector(
(state: RootState) => state.user.profile,
(profile) => ({
id: profile?.id,
name: profile?.username,
avatar: profile?.avatar
})
);
export function useUserProfile() {
return useAppSelector(selectUserProfile);
}
七、错误边界与异常处理
tsx
typescript
// components/ErrorBoundary/index.tsx
import React, { Component, ErrorInfo, ReactNode } from 'react';
import { Result, Button } from 'antd';
interface Props {
children: ReactNode;
fallback?: ReactNode;
}
interface State {
hasError: boolean;
error: Error | null;
}
export class ErrorBoundary extends Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = { hasError: false, error: null };
}
static getDerivedStateFromError(error: Error): State {
return { hasError: true, error };
}
componentDidCatch(error: Error, errorInfo: ErrorInfo) {
console.error('ErrorBoundary caught:', error, errorInfo);
// 上报错误到监控系统
reportError(error, errorInfo);
}
handleReset = () => {
this.setState({ hasError: false, error: null });
};
render() {
if (this.state.hasError) {
return this.props.fallback || (
<Result
status="error"
title="页面加载出错"
subTitle={this.state.error?.message || '发生了未知错误'}
extra={
<Button type="primary" onClick={this.handleReset}>
重试
</Button>
}
/>
);
}
return this.props.children;
}
}
八、总结
本文完整展示了React全家桶在企业级应用中的实战方案:
- 状态管理:Redux Toolkit提供类型安全的store配置,Ducks模式组织代码结构
- UI组件:Antd 5.x主题系统与组件深度集成,封装高阶组件提升复用性
- 逻辑复用:自定义Hooks封装数据请求、防抖、响应式等通用逻辑
- 请求层:Axios拦截器统一错误处理,与Redux联动实现全局状态更新
- 性能优化:组件懒加载、memo缓存、选择性订阅减少不必要渲染
完整的项目源码已同步至GitHub仓库,包含详细的部署文档和测试用例。在实际项目中,建议结合TypeScript严格模式、ESLint规范、Husky Git钩子保证代码质量,并接入Sentry等监控工具完善生产环境可观测性。