2020全新React教程全家桶实战redux+antd+React Hooks前端js视频

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.jsxmain.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;

注意 :我们在组件中使用了 useCallbackuseMemo 来避免不必要的重新渲染,这是 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 合理使用 useMemouseCallback

  • 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 构建布局和表单页面,并定制主题。
  • 在函数组件中熟练运用 useStateuseEffectuseCallbackuseMemo 等 Hooks。
  • 通过自定义 Hook 抽离业务逻辑,提高代码复用性。
  • 应用性能优化手段,提升应用体验。

这只是一个起点,你还可以在此基础上扩展更多功能,例如:

  • 接入 WebSocket 实现实时通知
  • 集成 ECharts 或 Recharts 实现数据可视化
  • 使用 MSW 或 MirageJS 进行 Mock 数据
  • 添加单元测试(Jest + React Testing Library)
  • 配置 CI/CD 自动化部署

希望本文能帮助你快速上手 React 全家桶,并为你的项目提供可靠的架构参考。如果你有任何问题或建议,欢迎在评论区交流讨论!

相关推荐
用户921080262861 小时前
Bubble 消息操作区改造:复制、重新生成和反馈
前端
LEE1 小时前
AI Agent 都在疯狂加功能,它说:我全砍了
前端·后端
张清悠1 小时前
用 TRAE Work 把 PRD + 设计稿注释,整理成 20 分钟可排期前端任务清单
前端
Brown.alexis2 小时前
es6知识点5-自备使用
前端·javascript·es6
杨利杰YJlio3 小时前
KB5121003更新详解:安全启动证书、AI组件、资源管理器与安装验证
前端·javascript·后端
紫菜猫一只3 小时前
Vue 3 与 React 常用写法对比
前端
jinggongszh3 小时前
使用AI协同开发产品条码规则功能——从“理解方案、原型、接口文档”到“落地真实前端代码”
前端·人工智能·mes系统·mes工程架构
渔夫正在掘金3 小时前
Cordis 中文教程:渐进式构建插件化应用
前端·node.js·ai编程
打呵欠的猫3 小时前
前端团队 3 个月 AI 实践复盘:哪些场景 ROI 最高,哪些是伪需求
前端·ai编程