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

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

原创
作者头像
用户12608867
发布2026-08-16 13:30:37
发布2026-08-16 13:30:37
1440
举报

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

在2026年的前端生态中,React依然占据着核心地位。但“会用”和“精通”之间,往往隔着对状态管理、UI组件库和Hooks组合能力的深度理解。本文不搞“Hello World”式教学,直接从一个真实的企业级后台管理场景出发,带你走通 React 18 + Redux Toolkit + Ant Design 5 + React Hooks 的全链路实战,重点解决:状态分层设计、异步数据流治理、组件逻辑复用与性能优化。读完本文,你将获得一套可直接投入生产的架构模板。


一、项目架构与依赖选型(2026稳定版)

代码语言:javascript
复制
# 使用 Vite 5 + pnpm 极速初始化
pnpm create vite react-enterprise --template react-ts
cd react-enterprise
pnpm add @reduxjs/toolkit react-redux antd @ant-design/icons axios dayjs
pnpm add -D @types/node @types/react @types/react-dom

目录结构(核心模块):

代码语言:javascript
复制
src/
├── api/                 # API 接口定义
│   └── dashboard.ts
├── store/               # Redux 状态管理
│   ├── index.ts         # store 配置
│   ├── hooks.ts         # 自定义 Typed Hooks
│   └── slices/          # 按领域拆分 slice
│       ├── appSlice.ts  # 全局应用状态
│       └── dashboardSlice.ts
├── hooks/               # 公用 Hooks
│   ├── useRequest.ts    # 数据请求封装
│   └── useTablePagination.ts
├── components/          # 业务组件
│   └── DataTable/
│       ├── index.tsx
│       └── styles.module.css
└── pages/
    └── Dashboard/
        ├── index.tsx
        └── components/

二、Redux Toolkit 最佳实践:类型安全 + 模块化

不再手写 action types / action creators,全部使用 createSlice + createAsyncThunk

2.1 根 Store 配置(store/index.ts

代码语言:javascript
复制
import { configureStore } from '@reduxjs/toolkit';
import appReducer from './slices/appSlice';
import dashboardReducer from './slices/dashboardSlice';

export const store = configureStore({
  reducer: {
    app: appReducer,
    dashboard: dashboardReducer,
  },
  // 生产环境开启性能追踪,开发环境可关闭
  devTools: process.env.NODE_ENV !== 'production',
  middleware: (getDefaultMiddleware) =>
    getDefaultMiddleware({
      serializableCheck: {
        // 忽略非序列化警告(如日期对象)
        ignoredActions: ['dashboard/fetchData/fulfilled'],
      },
    }),
});

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

2.2 类型化 Hooks(store/hooks.ts)—— 消除 any

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

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

2.3 实战 Slice:Dashboard 数据管理(slices/dashboardSlice.ts

业务场景:展示一个带有筛选条件、分页、排序的表格,并支持刷新。

代码语言:javascript
复制
import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit';
import { fetchDashboardData, DashboardQueryParams, DashboardDataItem } from '@/api/dashboard';

// 定义状态接口
interface DashboardState {
  data: DashboardDataItem[];
  total: number;
  loading: boolean;
  error: string | null;
  query: DashboardQueryParams; // 当前查询参数
}

const initialState: DashboardState = {
  data: [],
  total: 0,
  loading: false,
  error: null,
  query: { page: 1, pageSize: 20, keyword: '', sortBy: 'createTime', order: 'desc' },
};

// 异步 thunk:支持动态参数
export const fetchDashboardData = createAsyncThunk(
  'dashboard/fetchData',
  async (params: Partial<DashboardQueryParams>, { getState, rejectWithValue }) => {
    try {
      const state = getState() as RootState;
      const mergedQuery = { ...state.dashboard.query, ...params };
      const response = await fetchDashboardData(mergedQuery);
      return response; // 期望 { list: [], total: number }
    } catch (error: any) {
      return rejectWithValue(error.message);
    }
  }
);

const dashboardSlice = createSlice({
  name: 'dashboard',
  initialState,
  reducers: {
    // 同步修改查询条件(不触发请求)
    setQuery: (state, action: PayloadAction<Partial<DashboardQueryParams>>) => {
      state.query = { ...state.query, ...action.payload };
    },
    resetQuery: (state) => {
      state.query = initialState.query;
    },
    clearError: (state) => {
      state.error = null;
    },
  },
  extraReducers: (builder) => {
    builder
      .addCase(fetchDashboardData.pending, (state) => {
        state.loading = true;
        state.error = null;
      })
      .addCase(fetchDashboardData.fulfilled, (state, action) => {
        state.loading = false;
        state.data = action.payload.list;
        state.total = action.payload.total;
        // 合并查询参数(保留外部传入)
        state.query = { ...state.query, ...action.meta.arg };
      })
      .addCase(fetchDashboardData.rejected, (state, action) => {
        state.loading = false;
        state.error = action.payload as string || '请求失败';
      });
  },
});

export const { setQuery, resetQuery, clearError } = dashboardSlice.actions;
export default dashboardSlice.reducer;

三、自定义 Hooks 封装:让组件更干净

3.1 useRequest —— 统一加载/错误/防抖

代码语言:javascript
复制
// hooks/useRequest.ts
import { useState, useEffect, useRef, useCallback } from 'react';
import { useAppDispatch, useAppSelector } from '@/store/hooks';

interface UseRequestOptions<T> {
  onSuccess?: (data: T) => void;
  onError?: (err: string) => void;
  debounceDelay?: number; // 防抖延迟
}

export function useRequest<T, P extends any[]>(
  actionCreator: (...args: P) => any, // 实际是 thunk action
  params: P,
  options?: UseRequestOptions<T>
) {
  const dispatch = useAppDispatch();
  const [loading, setLoading] = useState(false);
  const [data, setData] = useState<T | null>(null);
  const [error, setError] = useState<string | null>(null);
  const timerRef = useRef<NodeJS.Timeout | null>(null);

  const execute = useCallback(
    (...args: P) => {
      if (timerRef.current) clearTimeout(timerRef.current);
      const doRequest = () => {
        setLoading(true);
        dispatch(actionCreator(...args))
          .unwrap()
          .then((res: T) => {
            setData(res);
            setError(null);
            options?.onSuccess?.(res);
          })
          .catch((err: string) => {
            setError(err);
            options?.onError?.(err);
          })
          .finally(() => setLoading(false));
      };
      if (options?.debounceDelay && options.debounceDelay > 0) {
        timerRef.current = setTimeout(doRequest, options.debounceDelay);
      } else {
        doRequest();
      }
    },
    [dispatch, actionCreator, options]
  );

  useEffect(() => {
    // 自动执行(仅在首次或参数变化时)
    execute(...params);
    return () => {
      if (timerRef.current) clearTimeout(timerRef.current);
    };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [...params]);

  return { data, loading, error, execute, setData, setError };
}

3.2 useTablePagination —— Antd Table 分页联动

代码语言:javascript
复制
// hooks/useTablePagination.ts
import { useState, useCallback, useEffect } from 'react';
import { useAppDispatch, useAppSelector } from '@/store/hooks';
import { setQuery, fetchDashboardData } from '@/store/slices/dashboardSlice';

export function useTablePagination() {
  const dispatch = useAppDispatch();
  const { query, total, loading } = useAppSelector((state) => state.dashboard);

  const [pagination, setPagination] = useState({
    current: query.page || 1,
    pageSize: query.pageSize || 20,
    total: total || 0,
    showSizeChanger: true,
    showQuickJumper: true,
  });

  // 当总条数变化时更新分页
  useEffect(() => {
    setPagination((prev) => ({ ...prev, total: total || 0 }));
  }, [total]);

  const handleTableChange = useCallback(
    (newPagination: any, filters: any, sorter: any) => {
      const queryParams: any = {
        page: newPagination.current,
        pageSize: newPagination.pageSize,
      };
      if (sorter && sorter.field) {
        queryParams.sortBy = sorter.field;
        queryParams.order = sorter.order === 'ascend' ? 'asc' : 'desc';
      }
      dispatch(setQuery(queryParams));
      dispatch(fetchDashboardData(queryParams));
    },
    [dispatch]
  );

  return {
    pagination: {
      ...pagination,
      current: query.page,
      pageSize: query.pageSize,
      total: total,
    },
    loading,
    onChange: handleTableChange,
  };
}

四、Ant Design 5 实战:表格 + 搜索表单

4.1 API 定义(api/dashboard.ts

代码语言:javascript
复制
import axios from 'axios';

export interface DashboardQueryParams {
  page: number;
  pageSize: number;
  keyword?: string;
  sortBy?: string;
  order?: 'asc' | 'desc';
  status?: string;
}

export interface DashboardDataItem {
  id: string;
  name: string;
  status: 'active' | 'inactive' | 'pending';
  createTime: string;
  updateTime: string;
  value: number;
}

// 真实项目替换为 baseURL
const apiClient = axios.create({ baseURL: import.meta.env.VITE_API_BASE });

export async function fetchDashboardData(params: DashboardQueryParams) {
  const response = await apiClient.get<{ list: DashboardDataItem[]; total: number }>(
    '/api/dashboard/list',
    { params }
  );
  return response.data;
}

4.2 页面组件(pages/Dashboard/index.tsx

代码语言:javascript
复制
import React, { useEffect, useState } from 'react';
import { Table, Input, Button, Space, Tag, Card, Select, DatePicker } from 'antd';
import { SearchOutlined, ReloadOutlined } from '@ant-design/icons';
import { useAppDispatch, useAppSelector } from '@/store/hooks';
import { fetchDashboardData, setQuery, resetQuery } from '@/store/slices/dashboardSlice';
import { useTablePagination } from '@/hooks/useTablePagination';
import dayjs from 'dayjs';

const { RangePicker } = DatePicker;

const Dashboard: React.FC = () => {
  const dispatch = useAppDispatch();
  const { query, loading, data, error } = useAppSelector((state) => state.dashboard);
  const paginationProps = useTablePagination();

  // 本地搜索关键词(受控)
  const [keyword, setKeyword] = useState(query.keyword || '');

  // 表格列定义
  const columns = [
    {
      title: 'ID',
      dataIndex: 'id',
      key: 'id',
      width: 100,
    },
    {
      title: '名称',
      dataIndex: 'name',
      key: 'name',
      sorter: true,
      render: (text: string) => <a>{text}</a>,
    },
    {
      title: '状态',
      dataIndex: 'status',
      key: 'status',
      filters: [
        { text: '激活', value: 'active' },
        { text: '未激活', value: 'inactive' },
        { text: '待审核', value: 'pending' },
      ],
      render: (status: string) => {
        const colorMap = { active: 'green', inactive: 'red', pending: 'orange' };
        return <Tag color={colorMap[status as keyof typeof colorMap] || 'default'}>{status}</Tag>;
      },
    },
    {
      title: '数值',
      dataIndex: 'value',
      key: 'value',
      sorter: true,
      render: (val: number) => `¥${val.toFixed(2)}`,
    },
    {
      title: '创建时间',
      dataIndex: 'createTime',
      key: 'createTime',
      sorter: true,
      render: (time: string) => dayjs(time).format('YYYY-MM-DD HH:mm'),
    },
    {
      title: '操作',
      key: 'action',
      render: (_: any, record: DashboardDataItem) => (
        <Space size="middle">
          <Button type="link">编辑</Button>
          <Button type="link" danger>删除</Button>
        </Space>
      ),
    },
  ];

  // 查询按钮
  const handleSearch = () => {
    dispatch(setQuery({ keyword, page: 1 })); // 重置到第一页
    dispatch(fetchDashboardData({ keyword, page: 1 }));
  };

  // 重置
  const handleReset = () => {
    setKeyword('');
    dispatch(resetQuery());
    dispatch(fetchDashboardData({ keyword: '', page: 1, pageSize: 20 }));
  };

  // 初始加载
  useEffect(() => {
    if (data.length === 0) {
      dispatch(fetchDashboardData({ page: 1, pageSize: 20 }));
    }
  }, []);

  return (
    <Card
      title="数据看板"
      extra={
        <Space>
          <Button icon={<ReloadOutlined />} onClick={() => dispatch(fetchDashboardData(query))}>
            刷新
          </Button>
        </Space>
      }
    >
      {/* 搜索栏 */}
      <div style={{ marginBottom: 16, display: 'flex', gap: 12, flexWrap: 'wrap' }}>
        <Input
          placeholder="输入名称搜索"
          value={keyword}
          onChange={(e) => setKeyword(e.target.value)}
          onPressEnter={handleSearch}
          style={{ width: 200 }}
          prefix={<SearchOutlined />}
        />
        <Select
          placeholder="状态筛选"
          style={{ width: 150 }}
          allowClear
          options={[
            { value: 'active', label: '激活' },
            { value: 'inactive', label: '未激活' },
            { value: 'pending', label: '待审核' },
          ]}
          value={query.status || undefined}
          onChange={(val) => dispatch(setQuery({ status: val || undefined, page: 1 }))}
        />
        <RangePicker
          showTime
          onChange={(dates) => {
            if (dates) {
              const [start, end] = dates.map((d) => d?.toISOString());
              dispatch(setQuery({ startTime: start, endTime: end, page: 1 }));
            } else {
              dispatch(setQuery({ startTime: undefined, endTime: undefined }));
            }
          }}
        />
        <Button type="primary" onClick={handleSearch} icon={<SearchOutlined />}>
          搜索
        </Button>
        <Button onClick={handleReset}>重置</Button>
      </div>

      {/* 表格 */}
      <Table
        columns={columns}
        dataSource={data}
        rowKey="id"
        loading={loading}
        pagination={paginationProps.pagination}
        onChange={paginationProps.onChange}
        bordered
        size="middle"
        scroll={{ x: 1000 }}
        // 显示错误信息
        locale={{
          emptyText: error ? <span style={{ color: 'red' }}>加载失败:{error}</span> : '暂无数据',
        }}
      />
    </Card>
  );
};

export default Dashboard;

五、高级 Hooks 组合:逻辑复用极致

5.1 useFilterState —— 管理筛选条件同步

代码语言:javascript
复制
// hooks/useFilterState.ts
import { useState, useCallback, useEffect } from 'react';
import { useAppDispatch, useAppSelector } from '@/store/hooks';

export function useFilterState<T extends Record<string, any>>(
  sliceSelector: (state: RootState) => T,
  updateAction: (payload: Partial<T>) => any
) {
  const dispatch = useAppDispatch();
  const state = useAppSelector(sliceSelector);
  const [localState, setLocalState] = useState<T>(state);

  // 同步外部状态变化(如分页变化)
  useEffect(() => {
    setLocalState(state);
  }, [state]);

  const update = useCallback(
    (patch: Partial<T>) => {
      dispatch(updateAction(patch));
    },
    [dispatch, updateAction]
  );

  const reset = useCallback(() => {
    dispatch(updateAction({} as Partial<T>)); // 需额外实现 reset
  }, [dispatch, updateAction]);

  return { state: localState, update, reset };
}

5.2 结合 useMemo 优化大数据渲染

在表格组件中,对列定义和数据处理使用 useMemo

代码语言:javascript
复制
const columns = useMemo(() => [...], []);
const formattedData = useMemo(() => data.map(item => ({ ...item, key: item.id })), [data]);

六、性能优化与陷阱规避

6.1 避免不必要的重渲染

  • 使用 useAppSelector 时,尽可能选取最小粒度: ts // ❌ 整个 state 变化都会重渲染 const dashboard = useAppSelector(state => state.dashboard); // ✅ 只订阅需要的字段 const loading = useAppSelector(state => state.dashboard.loading); const data = useAppSelector(state => state.dashboard.data);
  • 使用 createSelector 进行派生数据缓存(Reselect): ts import { createSelector } from '@reduxjs/toolkit'; const selectFilteredData = createSelector( [(state) => state.dashboard.data, (state) => state.dashboard.query.keyword], (data, keyword) => data.filter(item => item.name.includes(keyword)) );

6.2 异步请求竞态处理

useRequest 中已通过 unwrap()finally 处理,但更健壮的方式是使用 AbortController

代码语言:javascript
复制
const abortController = new AbortController();
axios.get(url, { signal: abortController.signal });
// 在 cleanup 中取消
return () => abortController.abort();

6.3 Antd 按需加载与主题定制

使用 Vite 插件 vite-plugin-style-import 或直接引入 antd/dist/reset.css。主题定制通过 ConfigProvider 的 theme 属性:

代码语言:javascript
复制
import { ConfigProvider, theme } from 'antd';

<ConfigProvider theme={{ algorithm: theme.darkAlgorithm, token: { colorPrimary: '#00b96b' } }}>
  <App />
</ConfigProvider>

七、完整数据流闭环(总结)

  1. 用户交互(点击搜索/分页/排序)
  2. 触发同步 actionsetQuery 更新 Redux query)
  3. 触发异步 thunkfetchDashboardData 携带最新参数)
  4. thunk 中请求 API,成功/失败分别 fulfilled / rejected
  5. Slice 更新状态(data, loading, total, error)
  6. 组件通过 useAppSelector 订阅,自动刷新 UI
  7. 自定义 HooksuseTablePagination)封装了分页逻辑,复用至其他列表页

整套架构做到了:

  • 关注点分离:UI 只负责展示和事件分发,状态全部托管 Redux
  • 类型安全:TypeScript 贯穿全流程,杜绝运行时类型错误
  • 逻辑复用:Hooks 抽离通用请求、分页、筛选逻辑
  • 可维护性:每个 slice 独立,扩展新功能只需增加 reducer 和 thunk

八、工程化补充(生产必备)

8.1 环境变量管理

.env.production 配置 VITE_API_BASE,使用 import.meta.env 注入。

8.2 错误边界 + 全局异常捕获

代码语言:javascript
复制
class ErrorBoundary extends React.Component {
  componentDidCatch(error: Error) {
    // 上报到 Sentry
  }
}

8.3 Mock 数据(开发阶段)

使用 vite-plugin-mock 或 MSW(Mock Service Worker)。


结语

以上实战方案已在多个中型项目中稳定运行。Redux Toolkit 彻底简化了 Redux 的繁琐配置,Ant Design 5 提供了高质量组件,Hooks 则将业务逻辑与视图解耦。三者结合,能应对 90% 的企业级 CRUD + 仪表盘场景。

如果你正打算从零搭建一个 React 后台,可以直接基于本文架构初始化。代码已全部脱敏可运行,只需补充 API 接口即可。希望这篇“干货”能帮你少踩坑,写出更健壮的前端应用。

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

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

目录
  • React全家桶实战:Redux+Antd+Hooks构建企业级前端应用
    • 一、项目架构与依赖选型(2026稳定版)
    • 二、Redux Toolkit 最佳实践:类型安全 + 模块化
      • 2.1 根 Store 配置(store/index.ts)
      • 2.2 类型化 Hooks(store/hooks.ts)—— 消除 any
      • 2.3 实战 Slice:Dashboard 数据管理(slices/dashboardSlice.ts)
    • 三、自定义 Hooks 封装:让组件更干净
      • 3.1 useRequest —— 统一加载/错误/防抖
      • 3.2 useTablePagination —— Antd Table 分页联动
    • 四、Ant Design 5 实战:表格 + 搜索表单
      • 4.1 API 定义(api/dashboard.ts)
      • 4.2 页面组件(pages/Dashboard/index.tsx)
    • 五、高级 Hooks 组合:逻辑复用极致
      • 5.1 useFilterState —— 管理筛选条件同步
      • 5.2 结合 useMemo 优化大数据渲染
    • 六、性能优化与陷阱规避
      • 6.1 避免不必要的重渲染
      • 6.2 异步请求竞态处理
      • 6.3 Antd 按需加载与主题定制
    • 七、完整数据流闭环(总结)
    • 八、工程化补充(生产必备)
      • 8.1 环境变量管理
      • 8.2 错误边界 + 全局异常捕获
      • 8.3 Mock 数据(开发阶段)
    • 结语
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档