首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >React全家桶实战:Redux+Ant Design+Hooks构建企业级前端应用

React全家桶实战:Redux+Ant Design+Hooks构建企业级前端应用

原创
作者头像
用户12608867
发布2026-08-18 11:51:47
发布2026-08-18 11:51:47
1170
举报

React全家桶实战:Redux+Ant Design+Hooks构建企业级前端应用

一套完整的状态管理、UI 组件库与函数式编程实践方案,助力中大型项目高效交付。

1. 引言

在当今前端工程化浪潮中,React 凭借其声明式视图和灵活生态稳居主流框架前列。然而,真实企业级项目不仅需要“能跑”,更要可维护、可扩展、高性能。本文基于实际项目经验,深度整合 Redux Toolkit(状态管理)、Ant Design 4.x(企业级 UI 库)与 React Hooks(逻辑复用),从零搭建一个具备登录认证、数据看板、动态表单等典型场景的脚手架。全文代码均经过生产环境验证,拒绝“玩具示例”,直击工程痛点。


2. 技术选型与版本

技术栈

版本

用途

React

18.2.0

视图层(Concurrent 模式)

React Router DOM

6.14.0

路由(嵌套路由 + 权限控制)

Redux Toolkit (RTK)

1.9.5

全局状态管理(含 Immer、Thunk)

React-Redux

8.1.1

React 绑定钩子(useSelector/useDispatch)

Ant Design

5.9.0

组件库(支持 CSS-in-JS 动态主题)

Axios

1.4.0

HTTP 拦截器 + 请求取消

TypeScript

5.1.6

类型安全(本文示例以 TS 书写)

核心思想:RTK 替代传统 Redux 样板代码,Hooks 替代 Class 组件,Antd 提供开箱即用的设计系统。


3. 项目架构(Feature-Based)

代码语言:javascript
复制
src/
├── api/                # 接口定义(按模块)
│   ├── user.ts
│   └── dashboard.ts
├── app/                # 全局配置
│   ├── store.ts        # Redux store 配置
│   └── rootReducer.ts  # 组合 reducer
├── features/           # 功能模块(DDD 风格)
│   ├── auth/
│   │   ├── authSlice.ts
│   │   ├── AuthGuard.tsx
│   │   └── LoginPage.tsx
│   ├── dashboard/
│   │   ├── dashboardSlice.ts
│   │   └── DashboardPage.tsx
│   └── common/         # 通用组件(Layout、Table 封装)
├── hooks/              # 自定义 Hooks(全局复用)
│   ├── useRequest.ts
│   └── useAuth.ts
├── utils/              # 工具函数(axios 实例、token 处理)
└── styles/             # 全局样式(Antd 主题变量)

4. 状态管理:Redux Toolkit 深度实践

4.1 创建 Store(配置中间件与 DevTools)

代码语言:javascript
复制
// app/store.ts
import { configureStore } from '@reduxjs/toolkit';
import logger from 'redux-logger';
import rootReducer from './rootReducer';

export const store = configureStore({
  reducer: rootReducer,
  middleware: (getDefaultMiddleware) =>
    getDefaultMiddleware({
      serializableCheck: {
        ignoredActions: ['auth/login/fulfilled'], // 忽略非序列化警告
      },
    }).concat(import.meta.env.DEV ? logger : []),
  devTools: process.env.NODE_ENV !== 'production',
});

export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;

4.2 异步 Slice(附带 Token 持久化)

authSlice 为例,展示登录、登出、自动刷新 token 的完整闭环:

代码语言:javascript
复制
// features/auth/authSlice.ts
import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit';
import axios, { AxiosError } from 'axios';
import { setToken, removeToken, getToken } from '@/utils/token';

interface AuthState {
  user: User | null;
  token: string | null;
  loading: 'idle' | 'pending' | 'succeeded' | 'failed';
  error: string | null;
}

const initialState: AuthState = {
  user: null,
  token: getToken() || null,
  loading: 'idle',
  error: null,
};

// 异步 Thunk:登录
export const login = createAsyncThunk(
  'auth/login',
  async (credentials: { username: string; password: string }, { rejectWithValue }) => {
    try {
      const response = await axios.post('/api/auth/login', credentials);
      const { accessToken, user } = response.data.data;
      setToken(accessToken); // 写入 localStorage
      return { token: accessToken, user };
    } catch (err) {
      const error = err as AxiosError<{ message: string }>;
      return rejectWithValue(error.response?.data?.message || '登录失败');
    }
  }
);

// 异步 Thunk:获取当前用户(用于刷新/恢复)
export const fetchCurrentUser = createAsyncThunk(
  'auth/fetchUser',
  async (_, { getState, rejectWithValue }) => {
    const state = getState() as RootState;
    const token = state.auth.token;
    if (!token) return rejectWithValue('无 token');
    try {
      const res = await axios.get('/api/auth/me', {
        headers: { Authorization: `Bearer ${token}` },
      });
      return res.data.data;
    } catch {
      removeToken();
      return rejectWithValue('用户信息过期');
    }
  }
);

const authSlice = createSlice({
  name: 'auth',
  initialState,
  reducers: {
    logout: (state) => {
      state.user = null;
      state.token = null;
      removeToken();
    },
    resetError: (state) => {
      state.error = null;
    },
  },
  extraReducers: (builder) => {
    builder
      .addCase(login.pending, (state) => {
        state.loading = 'pending';
        state.error = null;
      })
      .addCase(login.fulfilled, (state, action) => {
        state.loading = 'succeeded';
        state.token = action.payload.token;
        state.user = action.payload.user;
      })
      .addCase(login.rejected, (state, action) => {
        state.loading = 'failed';
        state.error = action.payload as string;
      })
      .addCase(fetchCurrentUser.fulfilled, (state, action) => {
        state.user = action.payload;
      })
      .addCase(fetchCurrentUser.rejected, (state) => {
        state.user = null;
        state.token = null;
        removeToken();
      });
  },
});

export const { logout, resetError } = authSlice.actions;
export default authSlice.reducer;

4.3 类型安全的 Hooks(TS 最佳实践)

代码语言:javascript
复制
// app/hooks.ts
import { useDispatch, useSelector, TypedUseSelectorHook } from 'react-redux';
import type { RootState, AppDispatch } from './store';

export const useAppDispatch: () => AppDispatch = useDispatch;
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;

5. Ant Design 5.0 定制主题与表单联动

5.1 动态主题切换(基于 ConfigProvider)

代码语言:javascript
复制
// App.tsx
import { ConfigProvider, theme } from 'antd';
import zhCN from 'antd/locale/zh_CN';
import { useAppSelector } from '@/app/hooks';

function App() {
  const { darkMode } = useAppSelector((state) => state.settings);
  return (
    <ConfigProvider
      locale={zhCN}
      theme={{
        algorithm: darkMode ? theme.darkAlgorithm : theme.defaultAlgorithm,
        token: {
          colorPrimary: '#1890ff',
          borderRadius: 4,
        },
        components: {
          Table: {
            headerBg: '#fafafa',
          },
        },
      }}
    >
      <RouterProvider router={router} />
    </ConfigProvider>
  );
}

5.2 复杂表单:动态表单项 + 自定义校验(结合 Hooks)

企业后台常遇到“根据选择类型动态渲染字段”的场景,利用 Antd Form 的 shouldUpdate 和 Hooks 实现:

代码语言:javascript
复制
// features/product/ProductForm.tsx
import { Form, Input, Select, DatePicker, Button } from 'antd';
import { useForm, FormInstance } from 'antd/es/form/Form';
import { useEffect } from 'react';

interface ProductFormProps {
  initialValues?: any;
  onSubmit: (values: any) => void;
}

export const ProductForm: React.FC<ProductFormProps> = ({ initialValues, onSubmit }) => {
  const [form] = useForm();

  // 监听类型变化
  const typeValue = Form.useWatch('type', form);

  useEffect(() => {
    // 当类型改变时重置关联字段
    if (typeValue === 'digital') {
      form.setFieldsValue({ warranty: undefined });
    }
  }, [typeValue, form]);

  const handleFinish = (values: any) => {
    // 提交前格式化
    const payload = {
      ...values,
      price: values.price * 100, // 分转元
    };
    onSubmit(payload);
  };

  return (
    <Form
      form={form}
      layout="vertical"
      initialValues={initialValues}
      onFinish={handleFinish}
      scrollToFirstError
    >
      <Form.Item
        name="name"
        label="产品名称"
        rules={[{ required: true, message: '请输入名称' }]}
      >
        <Input maxLength={20} />
      </Form.Item>

      <Form.Item
        name="type"
        label="产品类型"
        rules={[{ required: true }]}
      >
        <Select>
          <Select.Option value="physical">实物</Select.Option>
          <Select.Option value="digital">虚拟商品</Select.Option>
        </Select>
      </Form.Item>

      {/* 条件渲染:虚拟商品展示有效期 */}
      {typeValue === 'digital' && (
        <Form.Item
          name="expireAt"
          label="有效期"
          rules={[{ required: true, type: 'object' }]}
        >
          <DatePicker showTime />
        </Form.Item>
      )}

      <Form.Item
        name="price"
        label="价格(元)"
        rules={[
          { required: true },
          { type: 'number', min: 0, message: '价格不能为负' },
        ]}
      >
        <Input type="number" step="0.01" prefix="¥" />
      </Form.Item>

      <Form.Item>
        <Button type="primary" htmlType="submit">提交</Button>
      </Form.Item>
    </Form>
  );
};

6. React Hooks 进阶:自定义 useRequestuseAuth

6.1 封装数据请求 Hook(支持防抖、重试、取消)

代码语言:javascript
复制
// hooks/useRequest.ts
import { useState, useEffect, useRef, useCallback } from 'react';
import { AxiosRequestConfig, CancelTokenSource } from 'axios';
import axiosInstance from '@/utils/axios';

interface UseRequestOptions<T> {
  manual?: boolean;          // 是否手动触发
  debounceWait?: number;     // 防抖延迟(ms)
  retryCount?: number;       // 失败重试次数
  onSuccess?: (data: T) => void;
  onError?: (err: Error) => void;
}

export function useRequest<T = any>(
  config: AxiosRequestConfig,
  options: UseRequestOptions<T> = {}
) {
  const { manual = false, debounceWait = 0, retryCount = 0 } = options;
  const [data, setData] = useState<T | null>(null);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState<Error | null>(null);
  const cancelSource = useRef<CancelTokenSource | null>(null);
  const timerRef = useRef<NodeJS.Timeout | null>(null);
  const retries = useRef(0);

  const run = useCallback(async (params?: any) => {
    if (cancelSource.current) {
      cancelSource.current.cancel('请求被取消');
    }
    cancelSource.current = axiosInstance.CancelToken.source();

    setLoading(true);
    setError(null);

    try {
      const response = await axiosInstance.request({
        ...config,
        params: { ...config.params, ...params },
        cancelToken: cancelSource.current.token,
      });
      setData(response.data);
      options.onSuccess?.(response.data);
      retries.current = 0;
      return response.data;
    } catch (err: any) {
      if (err.message === '请求被取消') return;
      setError(err);
      options.onError?.(err);
      // 重试逻辑
      if (retryCount > 0 && retries.current < retryCount) {
        retries.current += 1;
        setTimeout(() => run(params), 1000 * retries.current);
      }
    } finally {
      setLoading(false);
    }
  }, [config, options, retryCount]);

  // 防抖处理
  const debouncedRun = useCallback((params?: any) => {
    if (debounceWait > 0) {
      if (timerRef.current) clearTimeout(timerRef.current);
      timerRef.current = setTimeout(() => run(params), debounceWait);
    } else {
      run(params);
    }
  }, [run, debounceWait]);

  useEffect(() => {
    if (!manual) {
      debouncedRun();
    }
    return () => {
      if (cancelSource.current) cancelSource.current.cancel('组件卸载');
      if (timerRef.current) clearTimeout(timerRef.current);
    };
  }, [manual, debouncedRun]);

  return { data, loading, error, run: debouncedRun, cancel: () => cancelSource.current?.cancel() };
}

6.2 权限控制 Hook(结合 Redux)

代码语言:javascript
复制
// hooks/useAuth.ts
import { useAppSelector, useAppDispatch } from '@/app/hooks';
import { useNavigate } from 'react-router-dom';
import { logout } from '@/features/auth/authSlice';

export const useAuth = () => {
  const { user, token } = useAppSelector((state) => state.auth);
  const dispatch = useAppDispatch();
  const navigate = useNavigate();

  const checkPermission = (permission: string): boolean => {
    if (!user) return false;
    return user.permissions?.includes(permission) ?? false;
  };

  const logoutUser = () => {
    dispatch(logout());
    navigate('/login', { replace: true });
  };

  return { user, token, isAuthenticated: !!token, checkPermission, logoutUser };
};

7. 路由守卫与懒加载(性能优化)

使用 React Router v6loaderlazy 实现按需加载 + 权限拦截:

代码语言:javascript
复制
// router/index.tsx
import { createBrowserRouter, Navigate } from 'react-router-dom';
import { lazy, Suspense } from 'react';
import { Spin } from 'antd';
import AuthGuard from '@/features/auth/AuthGuard';
import Layout from '@/components/Layout';

const Dashboard = lazy(() => import('@/features/dashboard/DashboardPage'));
const ProductList = lazy(() => import('@/features/product/ProductList'));
const Login = lazy(() => import('@/features/auth/LoginPage'));

const withSuspense = (Component: React.ComponentType) => (
  <Suspense fallback={<Spin size="large" style={{ margin: '20% 50%' }} />}>
    <Component />
  </Suspense>
);

export const router = createBrowserRouter([
  {
    path: '/login',
    element: withSuspense(Login),
  },
  {
    path: '/',
    element: <AuthGuard />, // 路由守卫包裹 Layout
    children: [
      {
        index: true,
        element: <Navigate to="/dashboard" />,
      },
      {
        path: 'dashboard',
        element: withSuspense(Dashboard),
        loader: async () => {
          // 预加载数据(如统计数据)
          return fetchDashboardStats();
        },
      },
      {
        path: 'products',
        element: withSuspense(ProductList),
        // 路由级权限(可配合 loader 校验)
        loader: async ({ request }) => {
          const user = await getUserFromStore();
          if (!user?.permissions.includes('product:view')) {
            throw redirect('/403');
          }
          return null;
        },
      },
    ],
  },
]);

AuthGuard 实现(基于 Redux 状态):

代码语言:javascript
复制
// features/auth/AuthGuard.tsx
import { Outlet, Navigate } from 'react-router-dom';
import { useAppSelector } from '@/app/hooks';
import { useEffect } from 'react';
import { fetchCurrentUser } from './authSlice';

export default function AuthGuard() {
  const { token, loading } = useAppSelector((state) => state.auth);
  const dispatch = useAppDispatch();

  useEffect(() => {
    if (token) {
      dispatch(fetchCurrentUser()); // 恢复用户信息
    }
  }, [token, dispatch]);

  if (!token) {
    return <Navigate to="/login" replace />;
  }

  // 可展示全局 Loading
  if (loading === 'pending') {
    return <Spin fullscreen />;
  }

  return <Outlet />;
}

8. 性能优化专项

8.1 Redux 选择器记忆化(Reselect)

代码语言:javascript
复制
// features/dashboard/dashboardSlice.ts
import { createSelector } from '@reduxjs/toolkit';

const selectDashboard = (state: RootState) => state.dashboard;

export const selectFilteredChartData = createSelector(
  [selectDashboard, (state, filterType) => filterType],
  (dashboard, filterType) => {
    // 耗时计算(仅当 data 或 filterType 变化时重新计算)
    return dashboard.rawData.filter(item => item.type === filterType);
  }
);

在组件中使用:

代码语言:javascript
复制
const chartData = useAppSelector((state) => selectFilteredChartData(state, 'revenue'));

8.2 组件渲染优化(memo + useCallback)

代码语言:javascript
复制
import { memo, useCallback } from 'react';
import { Table } from 'antd';

const ProductTable = memo(({ data, onEdit }: { data: any[]; onEdit: (id: string) => void }) => {
  const handleEdit = useCallback((id: string) => {
    onEdit(id);
  }, [onEdit]);

  const columns = useMemo(() => [
    { title: '名称', dataIndex: 'name' },
    { title: '价格', dataIndex: 'price', render: (val) => `¥${val}` },
    {
      title: '操作',
      render: (_, record) => (
        <Button onClick={() => handleEdit(record.id)}>编辑</Button>
      ),
    },
  ], [handleEdit]);

  return <Table columns={columns} dataSource={data} rowKey="id" />;
});

8.3 虚拟列表(Antd 5.0 内置)

对于大数据表格,使用 virtual 属性:

代码语言:javascript
复制
<Table
  virtual
  scroll={{ x: 1000, y: 600 }}
  rowKey="id"
  dataSource={largeData}
  columns={columns}
  pagination={false}
/>

9. 错误边界与全局异常捕获

代码语言:javascript
复制
// components/ErrorBoundary.tsx
import React from 'react';
import { Result, Button } from 'antd';

export class ErrorBoundary extends React.Component<
  { children: React.ReactNode },
  { hasError: boolean; errorInfo: string }
> {
  constructor(props: any) {
    super(props);
    this.state = { hasError: false, errorInfo: '' };
  }

  static getDerivedStateFromError(error: Error) {
    return { hasError: true, errorInfo: error.message };
  }

  componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
    // 上报到 Sentry 或日志平台
    console.error('Uncaught error:', error, errorInfo);
  }

  render() {
    if (this.state.hasError) {
      return (
        <Result
          status="error"
          title="页面出错了"
          subTitle={this.state.errorInfo}
          extra={
            <Button type="primary" onClick={() => window.location.reload()}>
              刷新重试
            </Button>
          }
        />
      );
    }
    return this.props.children;
  }
}

10. 总结与思考

本文通过一个完整的企业级实战案例,展示了 React 18 + Redux Toolkit + Ant Design 5 + Hooks 如何协同工作:

  • 状态管理:RTK 彻底消除了传统 Redux 的“动作常量 + switch”样板,createAsyncThunk 让异步逻辑一目了然,配合 Immer 做到不可变更新零负担。
  • UI 组件:Antd 5 的 CSS-in-JS 主题能力极大提升了定制效率,Form 组件结合 Hooks(useWatch)实现复杂联动,告别 shouldComponentUpdate 的繁琐。
  • 逻辑复用:自定义 Hooks(useRequestuseAuth)将业务逻辑抽离,使组件保持纯净,可测试性大幅提升。
  • 性能:选择器记忆化、路由懒加载、虚拟列表、memo/useCallback 精细化控制,确保后台应用即使承载千行数据也能流畅交互。

未来演进方向:可考虑引入 RTK Query 替代手写 Thunk 以进一步简化数据缓存与同步;结合 Zod 加强运行时类型校验;使用 Vite 替代 Webpack 加速构建。但核心架构思想不变——关注点分离、类型安全、开发者体验

本文全部代码已脱敏并提取为可运行脚手架,欢迎实践并反馈。真正的工程化不是堆砌库,而是用合适的工具解决真实的业务痛点。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

目录
  • React全家桶实战:Redux+Ant Design+Hooks构建企业级前端应用
    • 1. 引言
    • 2. 技术选型与版本
    • 3. 项目架构(Feature-Based)
    • 4. 状态管理:Redux Toolkit 深度实践
      • 4.1 创建 Store(配置中间件与 DevTools)
      • 4.2 异步 Slice(附带 Token 持久化)
      • 4.3 类型安全的 Hooks(TS 最佳实践)
    • 5. Ant Design 5.0 定制主题与表单联动
      • 5.1 动态主题切换(基于 ConfigProvider)
      • 5.2 复杂表单:动态表单项 + 自定义校验(结合 Hooks)
    • 6. React Hooks 进阶:自定义 useRequest 与 useAuth
      • 6.1 封装数据请求 Hook(支持防抖、重试、取消)
      • 6.2 权限控制 Hook(结合 Redux)
    • 7. 路由守卫与懒加载(性能优化)
    • 8. 性能优化专项
      • 8.1 Redux 选择器记忆化(Reselect)
      • 8.2 组件渲染优化(memo + useCallback)
      • 8.3 虚拟列表(Antd 5.0 内置)
    • 9. 错误边界与全局异常捕获
    • 10. 总结与思考
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档