React全家桶实战:Redux + Antd + React Hooks 构建企业级后台应用
前言
在当今的前端开发中,React 已然成为最流行的 UI 库之一。而围绕 React 的生态体系------"全家桶"------更是为大型应用提供了路由、状态管理、UI 组件、构建工具等一整套解决方案。本文将从零开始,带领大家使用 React 18 + Redux Toolkit + Ant Design 5 + React Hooks 搭建一个具有典型业务场景的后台管理应用原型。通过实战,你将掌握:
- 如何使用
create-react-app或 Vite 快速初始化项目 - Redux Toolkit 的现代化状态管理(包括异步 thunk)
- Ant Design 5 的主题定制与组件使用
- React Hooks(useState, useEffect, useMemo, useCallback, 自定义 Hook)的深度实践
- 结合 React Router v6 实现路由与权限控制
无论你是初学者还是有一定经验的开发者,本文都会为你提供清晰可复用的代码范式,帮助你构建健壮、可维护的企业级应用。
1. 技术选型与项目初始化
1.1 核心依赖
| 技术 | 版本 | 用途 |
|---|---|---|
| React | 18.2.0 | UI 框架 |
| React DOM | 18.2.0 | DOM 渲染 |
| React Router | 6.14.0 | 路由管理 |
| Redux Toolkit | 1.9.5 | 状态管理(含 immer, thunk) |
| React-Redux | 8.0.5 | React 绑定 |
| Ant Design | 5.7.0 | 企业级 UI 组件库 |
| Axios | 1.4.0 | HTTP 请求 |
| Vite | 4.3.9 | 构建工具(快速替代 CRA) |
1.2 使用 Vite 创建项目
bash
复制
下载
bash
# 创建项目
npm create vite@latest react-family-demo -- --template react
cd react-family-demo
# 安装核心依赖
npm install react-router-dom @reduxjs/toolkit react-redux antd axios
# 安装开发依赖
npm install -D @types/react @types/react-dom eslint prettier
推荐使用 Vite 代替 create-react-app,启动和热更新速度更快。
1.3 目录结构设计
良好的目录结构是项目可维护性的基石。我们采用 功能模块化 的划分方式:
text
复制
下载
bash
src/
├── api/ # API 请求函数
│ └── user.js
├── assets/ # 静态资源(图片、字体等)
├── components/ # 公共 UI 组件
│ ├── Layout/
│ └── Common/
├── pages/ # 页面级组件
│ ├── Dashboard/
│ ├── Login/
│ └── UserList/
├── hooks/ # 自定义 Hooks
│ └── useAuth.js
├── store/ # Redux 状态管理
│ ├── index.js # store 配置
│ └── slices/ # 各个 slice
│ ├── userSlice.js
│ └── appSlice.js
├── utils/ # 工具函数
│ ├── request.js # axios 封装
│ └── storage.js
├── App.jsx
├── main.jsx
└── index.css
2. 封装 Axios 与 API 服务
在实际项目中,我们需要统一处理请求拦截、响应拦截、错误提示和 Token 管理。
2.1 创建 utils/request.js
javascript
复制
下载
javascript
import axios from 'axios';
import { message } from 'antd';
// 创建 axios 实例
const request = axios.create({
baseURL: import.meta.env.VITE_API_BASE_URL || '/api',
timeout: 10000,
headers: { 'Content-Type': 'application/json' },
});
// 请求拦截器:添加 Token
request.interceptors.request.use(
(config) => {
const token = localStorage.getItem('token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
(error) => Promise.reject(error)
);
// 响应拦截器:统一处理错误码
request.interceptors.response.use(
(response) => {
const res = response.data;
// 假设后端返回格式:{ code: 0, data: ..., msg: '' }
if (res.code !== 0) {
message.error(res.msg || '请求失败');
// 如果 token 失效,跳转登录
if (res.code === 401) {
localStorage.removeItem('token');
window.location.href = '/login';
}
return Promise.reject(new Error(res.msg || 'Error'));
}
return res;
},
(error) => {
message.error(error.message || '网络异常');
return Promise.reject(error);
}
);
export default request;
2.2 API 模块示例(api/user.js)
javascript
复制
下载
javascript
import request from '@/utils/request';
export const login = (data) => request.post('/auth/login', data);
export const getUserList = (params) => request.get('/users', { params });
export const createUser = (data) => request.post('/users', data);
export const updateUser = (id, data) => request.put(`/users/${id}`, data);
export const deleteUser = (id) => request.delete(`/users/${id}`);
3. 状态管理 ------ Redux Toolkit 深度实践
Redux Toolkit(RTK)是官方推荐的 Redux 写法,它极大地简化了配置,并集成了 Immer、Thunk 等中间件。
3.1 创建 store/index.js
javascript
复制
下载
typescript
import { configureStore } from '@reduxjs/toolkit';
import userReducer from './slices/userSlice';
import appReducer from './slices/appSlice';
export const store = configureStore({
reducer: {
user: userReducer,
app: appReducer,
},
// 开发时启用 Redux DevTools
devTools: process.env.NODE_ENV !== 'production',
});
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
3.2 创建 User Slice(含异步 Thunk)
store/slices/userSlice.js
javascript
复制
下载
javascript
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
import { login as loginApi, getUserList, deleteUser } from '@/api/user';
import { message } from 'antd';
// 异步 thunk:登录
export const loginAsync = createAsyncThunk(
'user/login',
async (loginData, { rejectWithValue }) => {
try {
const res = await loginApi(loginData);
// 假设返回 { data: { token, userInfo } }
return res.data;
} catch (error) {
return rejectWithValue(error.message);
}
}
);
// 异步 thunk:获取用户列表
export const fetchUserListAsync = createAsyncThunk(
'user/fetchList',
async (params, { rejectWithValue }) => {
try {
const res = await getUserList(params);
return res.data;
} catch (error) {
return rejectWithValue(error.message);
}
}
);
// 异步 thunk:删除用户
export const deleteUserAsync = createAsyncThunk(
'user/delete',
async (id, { rejectWithValue, dispatch }) => {
try {
await deleteUser(id);
// 删除成功后重新拉取列表
dispatch(fetchUserListAsync());
message.success('删除成功');
return id;
} catch (error) {
return rejectWithValue(error.message);
}
}
);
const initialState = {
userInfo: null,
token: localStorage.getItem('token') || null,
userList: [],
loading: false,
error: null,
};
const userSlice = createSlice({
name: 'user',
initialState,
reducers: {
// 同步 reducer:退出登录
logout: (state) => {
state.userInfo = null;
state.token = null;
localStorage.removeItem('token');
},
// 清除错误
clearError: (state) => {
state.error = null;
},
},
extraReducers: (builder) => {
builder
// 登录
.addCase(loginAsync.pending, (state) => {
state.loading = true;
state.error = null;
})
.addCase(loginAsync.fulfilled, (state, action) => {
state.loading = false;
state.userInfo = action.payload.userInfo;
state.token = action.payload.token;
localStorage.setItem('token', action.payload.token);
message.success('登录成功');
})
.addCase(loginAsync.rejected, (state, action) => {
state.loading = false;
state.error = action.payload;
message.error(action.payload || '登录失败');
})
// 获取用户列表
.addCase(fetchUserListAsync.pending, (state) => {
state.loading = true;
})
.addCase(fetchUserListAsync.fulfilled, (state, action) => {
state.loading = false;
state.userList = action.payload;
})
.addCase(fetchUserListAsync.rejected, (state, action) => {
state.loading = false;
state.error = action.payload;
});
},
});
export const { logout, clearError } = userSlice.actions;
export default userSlice.reducer;
3.3 在 React 中注入 Store
main.jsx
jsx
复制
下载
javascript
import React from 'react';
import ReactDOM from 'react-dom/client';
import { Provider } from 'react-redux';
import { store } from './store';
import App from './App';
import './index.css';
import 'antd/dist/reset.css'; // Ant Design 5 重置样式
ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode>
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>
);
4. 路由配置与权限控制(React Router v6)
4.1 定义路由表
router/index.js
jsx
复制
下载
javascript
import { createBrowserRouter } from 'react-router-dom';
import Layout from '@/components/Layout';
import Login from '@/pages/Login';
import Dashboard from '@/pages/Dashboard';
import UserList from '@/pages/UserList';
import PrivateRoute from './PrivateRoute'; // 权限组件
export const router = createBrowserRouter([
{
path: '/login',
element: <Login />,
},
{
path: '/',
element: <PrivateRoute />, // 拦截未登录
children: [
{
path: '/',
element: <Layout />,
children: [
{ index: true, element: <Dashboard /> },
{ path: 'users', element: <UserList /> },
// 可扩展更多子路由
],
},
],
},
]);
4.2 权限守卫组件 PrivateRoute
jsx
复制
下载
javascript
import { Navigate, Outlet } from 'react-router-dom';
import { useSelector } from 'react-redux';
const PrivateRoute = () => {
const token = useSelector((state) => state.user.token);
// 也可以检查用户信息是否存在
return token ? <Outlet /> : <Navigate to="/login" replace />;
};
export default PrivateRoute;
4.3 在 App.jsx 中渲染路由
jsx
复制
下载
javascript
import { RouterProvider } from 'react-router-dom';
import { router } from './router';
function App() {
return <RouterProvider router={router} />;
}
export default App;
5. Ant Design 5 主题定制与布局
Ant Design 5 引入了 CSS-in-JS 的动态主题能力,我们可以通过 ConfigProvider 定制全局主题。
5.1 定制主题(App.jsx 或 main.jsx 中)
jsx
复制
下载
javascript
import { ConfigProvider, theme } from 'antd';
function App() {
return (
<ConfigProvider
theme={{
token: {
colorPrimary: '#00b96b',
borderRadius: 4,
},
components: {
Button: {
colorPrimary: '#00b96b',
algorithm: true, // 启用算法
},
},
}}
>
<RouterProvider router={router} />
</ConfigProvider>
);
}
5.2 基本布局组件(components/Layout/index.jsx)
javascript
import { Layout, Menu, Avatar, Dropdown, Space } from 'antd';
import { Outlet, useNavigate } from 'react-router-dom';
import { useDispatch, useSelector } from 'react-redux';
import { logout } from '@/store/slices/userSlice';
import { UserOutlined, LogoutOutlined } from '@ant-design/icons';
const { Header, Sider, Content } = Layout;
const AppLayout = () => {
const navigate = useNavigate();
const dispatch = useDispatch();
const userInfo = useSelector((state) => state.user.userInfo);
const handleLogout = () => {
dispatch(logout());
navigate('/login');
};
const menuItems = [
{ key: '1', label: '用户管理', onClick: () => navigate('/users') },
{ key: '2', label: '仪表盘', onClick: () => navigate('/') },
];
const userMenu = {
items: [
{ key: 'profile', label: '个人中心', icon: <UserOutlined /> },
{ key: 'logout', label: '退出登录', icon: <LogoutOutlined />, onClick: handleLogout },
],
};
return (
<Layout style={{ minHeight: '100vh' }}>
<Sider theme="light">
<div style={{ height: 64, padding: 16, fontSize: 18, fontWeight: 'bold' }}>
React全家桶
</div>
<Menu mode="inline" items={menuItems} />
</Sider>
<Layout>
<Header style={{ background: '#fff', padding: '0 24px', display: 'flex', justifyContent: 'space-between' }}>
<div></div>
<Space>
<span>{userInfo?.name || 'Admin'}</span>
<Dropdown menu={userMenu} placement="bottomRight">
<Avatar icon={<UserOutlined />} />
</Dropdown>
</Space>
</Header>
<Content style={{ margin: '24px 16px', padding: 24, background: '#fff', borderRadius: 8 }}>
<Outlet />
</Content>
</Layout>
</Layout>
);
};
export default AppLayout;
6. 页面实战:用户管理列表(Hooks + Redux)
6.1 用户列表页面(pages/UserList/index.jsx)
我们将使用 Ant Design 的 Table、Modal、Form 等组件,结合 Redux 的异步 thunk 进行增删改查。
ini
import { useEffect, useState, useCallback, useMemo } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { Table, Button, Modal, Form, Input, Space, Popconfirm, message } from 'antd';
import { fetchUserListAsync, deleteUserAsync } from '@/store/slices/userSlice';
import { createUser, updateUser } from '@/api/user';
const UserList = () => {
const dispatch = useDispatch();
const { userList, loading } = useSelector((state) => state.user);
const [modalVisible, setModalVisible] = useState(false);
const [editingUser, setEditingUser] = useState(null);
const [form] = Form.useForm();
// 获取列表数据
const fetchData = useCallback(() => {
dispatch(fetchUserListAsync({ page: 1, size: 10 }));
}, [dispatch]);
useEffect(() => {
fetchData();
}, [fetchData]);
// 打开新增/编辑弹窗
const openModal = useCallback((record = null) => {
setEditingUser(record);
if (record) {
form.setFieldsValue(record);
} else {
form.resetFields();
}
setModalVisible(true);
}, [form]);
// 关闭弹窗
const closeModal = useCallback(() => {
setModalVisible(false);
setEditingUser(null);
form.resetFields();
}, [form]);
// 提交表单(新增或编辑)
const handleSubmit = useCallback(async () => {
try {
const values = await form.validateFields();
if (editingUser) {
await updateUser(editingUser.id, values);
message.success('更新成功');
} else {
await createUser(values);
message.success('创建成功');
}
closeModal();
fetchData(); // 刷新列表
} catch (error) {
console.error('Submit error:', error);
}
}, [editingUser, form, closeModal, fetchData]);
// 删除用户
const handleDelete = useCallback((id) => {
dispatch(deleteUserAsync(id));
}, [dispatch]);
// 表格列定义(使用 useMemo 缓存)
const columns = useMemo(() => [
{
title: 'ID',
dataIndex: 'id',
key: 'id',
},
{
title: '姓名',
dataIndex: 'name',
key: 'name',
},
{
title: '邮箱',
dataIndex: 'email',
key: 'email',
},
{
title: '角色',
dataIndex: 'role',
key: 'role',
},
{
title: '操作',
key: 'action',
render: (_, record) => (
<Space>
<Button type="link" onClick={() => openModal(record)}>编辑</Button>
<Popconfirm
title="确认删除?"
onConfirm={() => handleDelete(record.id)}
okText="确认"
cancelText="取消"
>
<Button type="link" danger>删除</Button>
</Popconfirm>
</Space>
),
},
], [openModal, handleDelete]);
return (
<div>
<div style={{ marginBottom: 16 }}>
<Button type="primary" onClick={() => openModal()}>新增用户</Button>
</div>
<Table
rowKey="id"
dataSource={userList}
columns={columns}
loading={loading}
pagination={{ pageSize: 10 }}
/>
<Modal
title={editingUser ? '编辑用户' : '新增用户'}
open={modalVisible}
onOk={handleSubmit}
onCancel={closeModal}
okText="保存"
cancelText="取消"
>
<Form form={form} layout="vertical">
<Form.Item
name="name"
label="姓名"
rules={[{ required: true, message: '请输入姓名' }]}
>
<Input />
</Form.Item>
<Form.Item
name="email"
label="邮箱"
rules={[{ required: true, type: 'email', message: '请输入有效邮箱' }]}
>
<Input />
</Form.Item>
<Form.Item
name="role"
label="角色"
rules={[{ required: true, message: '请输入角色' }]}
>
<Input />
</Form.Item>
</Form>
</Modal>
</div>
);
};
export default UserList;
注意 :我们在组件中使用了
useCallback和useMemo来避免不必要的重新渲染,这是 Hooks 性能优化的重要实践。
7. 自定义 Hook 封装业务逻辑
将可复用的逻辑抽离为自定义 Hook,是 React Hooks 的核心优势之一。例如,我们封装一个 useAuth 用于鉴权相关操作。
7.1 hooks/useAuth.js
javascript
复制
下载
javascript
import { useSelector, useDispatch } from 'react-redux';
import { useNavigate } from 'react-router-dom';
import { loginAsync, logout } from '@/store/slices/userSlice';
import { useCallback } from 'react';
export const useAuth = () => {
const dispatch = useDispatch();
const navigate = useNavigate();
const { userInfo, token, loading } = useSelector((state) => state.user);
const login = useCallback(async (loginData) => {
const resultAction = await dispatch(loginAsync(loginData));
if (loginAsync.fulfilled.match(resultAction)) {
navigate('/');
return { success: true };
} else {
return { success: false, error: resultAction.payload };
}
}, [dispatch, navigate]);
const logoutUser = useCallback(() => {
dispatch(logout());
navigate('/login');
}, [dispatch, navigate]);
return {
userInfo,
token,
loading,
isAuthenticated: !!token,
login,
logout: logoutUser,
};
};
然后在登录页面中使用:
jsx
复制
下载
javascript
import { Form, Input, Button } from 'antd';
import { useAuth } from '@/hooks/useAuth';
const Login = () => {
const { login, loading } = useAuth();
const onFinish = (values) => {
login(values);
};
return (
<Form onFinish={onFinish}>
<Form.Item name="username" rules={[{ required: true }]}>
<Input placeholder="用户名" />
</Form.Item>
<Form.Item name="password" rules={[{ required: true }]}>
<Input.Password placeholder="密码" />
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit" loading={loading}>
登录
</Button>
</Form.Item>
</Form>
);
};
8. 性能优化与最佳实践
8.1 使用 React.memo 避免无意义渲染
对于展示型组件,使用 React.memo 进行浅比较缓存。
jsx
复制
下载
javascript
const UserCard = React.memo(({ user }) => {
return <div>{user.name}</div>;
});
8.2 合理使用 useMemo 和 useCallback
useMemo缓存计算结果(如过滤后的列表)。useCallback缓存函数引用,避免子组件不必要的更新。
8.3 Redux 中选择器优化
使用 createSelector(来自 Reselect)缓存派生数据,避免重复计算。
javascript
复制
下载
ini
import { createSelector } from '@reduxjs/toolkit';
const selectUserList = (state) => state.user.userList;
const selectFilteredUsers = createSelector(
[selectUserList],
(users) => users.filter(user => user.role === 'admin')
);
8.4 懒加载路由
使用 React.lazy 和 Suspense 实现代码分割。
jsx
复制
下载
javascript
const UserList = React.lazy(() => import('@/pages/UserList'));
// 在路由配置中使用 Suspense 包裹
9. 总结与展望
通过本文的实战,我们完成了一个基于 React 18 + Redux Toolkit + Ant Design 5 + React Router v6 的后台管理应用的核心模块。你学到了:
- 使用 Vite 快速搭建 React 项目,并设计合理的目录结构。
- 封装 Axios 实例,处理请求/响应拦截和 Token 管理。
- 使用 Redux Toolkit 创建 Slice 和异步 Thunk,管理全局状态。
- 配置 React Router v6,实现路由嵌套和权限守卫。
- 利用 Ant Design 5 构建布局和表单页面,并定制主题。
- 在函数组件中熟练运用
useState、useEffect、useCallback、useMemo等 Hooks。 - 通过自定义 Hook 抽离业务逻辑,提高代码复用性。
- 应用性能优化手段,提升应用体验。
这只是一个起点,你还可以在此基础上扩展更多功能,例如:
- 接入 WebSocket 实现实时通知
- 集成 ECharts 或 Recharts 实现数据可视化
- 使用 MSW 或 MirageJS 进行 Mock 数据
- 添加单元测试(Jest + React Testing Library)
- 配置 CI/CD 自动化部署
希望本文能帮助你快速上手 React 全家桶,并为你的项目提供可靠的架构参考。如果你有任何问题或建议,欢迎在评论区交流讨论!