使用自定义 Hook useTable 简化 Ant Design Table 的开发

在前端开发中,表格(Table)是一个非常常见的组件,尤其是在管理后台系统中。Ant Design 提供了强大的 Table 组件,但在实际使用中,我们往往需要处理分页、数据请求、加载状态等逻辑。为了减少重复代码,我们可以将这些逻辑封装到一个自定义的 React Hook 中。本文将分享一个名为 useTable 的自定义 Hook,它可以帮助我们更高效地处理表格相关的逻辑。

文末附有详细代码

1. useTable 的功能介绍

useTable 是一个自定义 Hook,它封装了 Ant Design Table 的常用功能,包括:

  • 分页管理:自动处理分页逻辑,支持分页器的配置。

  • 数据请求:支持通过接口请求获取表格数据,并处理加载状态。

  • 查询功能 :提供 query 方法,支持根据条件查询数据。

  • 初始数据:支持传入初始数据,方便开发调试。

  • 自定义配置:支持自定义表格的样式、滚动条、分页器等配置。

2. useTable 的使用示例

首先,我们来看一个简单的使用示例:

php 复制代码
import React from 'react';
import useTable from './useTable';

const MyTable = () => {
  const [TableComponent, { query }] = useTable({
    columns: [
      {
        title: '序号',
        align: 'center',
        dataIndex: 'code',
      },
      {
        title: '姓名',
        align: 'center',
        dataIndex: 'name',
      },
    ],
    scroll: { x: 1500 },
    customProps: {
      api: 'api/getTable',
    },
  });

  const handleQuery = () => {
    query({ name: '小李' });
  };

  return (
    <div>
      <button onClick={handleQuery}>查询</button>
      {TableComponent}
    </div>
  );
};

export default MyTable;

在这个示例中,我们通过 useTable 创建了一个表格组件,并提供了查询功能。点击按钮后,表格会根据条件重新请求数据并渲染。

3. useTable 的实现细节

接下来,我们来看一下 useTable 的具体实现代码。

3.1 分页常量定义

首先,我们定义了一些分页的常量:

yaml 复制代码
const PAGINATION_INIT = {
  showTotal: (total) => `共 ${total} 条数据`,
  pageSize: 10,
  current: 1,
  showSizeChanger: true,
  pageSizeOptions: [10, 20, 50, 100],
  total: 0,
};

这些常量用于初始化分页器的配置。

3.2 数据请求逻辑

我们使用 useTableDataFetcher 这个自定义 Hook 来处理数据的请求逻辑:

ini 复制代码
const useTableDataFetcher = ({ api, method, params, pagination, autoRequest, responseAfter }) => {
  const [loading, setLoading] = useState(false);
  const [data, setData] = useState([]);
  const [total, setTotal] = useState(0);
  const firstRequestRef = useRef(false); // 判断第一次进入页面

  const fetchData = useCallback(async () => {
    if (!api) return;

    try {
      setLoading(true);

      const { pageSize, current } = pagination;
      const queryParams = {
        limit: pageSize,
        start: (current - 1) * pageSize,
        ...params,
      };
      const res = await request[method](api, queryParams);

      if (res?.success) {
        const value = res.value || res;
        const processedData =
          typeof responseAfter === 'function'
            ? await responseAfter(value.rows || [])
            : value.rows || [];

        setData(processedData);
        setTotal(value.results || 0);
      } else {
        message.error(res?.errorMsg || '数据获取失败');
      }
    } catch (error) {
      if (error.name !== 'AbortError') {
        message.error('请求失败,请稍后重试');
      }
    } finally {
      setLoading(false);
    }
  }, [api, method, params, pagination, responseAfter]);

  useEffect(() => {
    // 不自动请求 && 是第一次执行
    if (!autoRequest && !firstRequestRef.current) {
      firstRequestRef.current = true;
      return;
    } else {
      fetchData();
    }
  }, [fetchData]);

  return { loading, data, total };
};

这个 Hook 负责发送请求、处理响应数据,并管理加载状态。

3.3 useTable 主逻辑

最后,我们来看 useTable 的主逻辑:

ini 复制代码
const useTable = (props) => {
  const {
    scroll = false,
    rowKey = 'id',
    border = false,
    className = '',
    columns = [],
    pagination: propPagination = {},
    customProps = {},
    isShowTableData = true,
    initialData = [],
    ...rest
  } = props;

  // 分页状态管理
  const [pagination, setPagination] = useState({
    ...PAGINATION_INIT,
    ...propPagination,
  });

  // 请求参数管理
  const [queryParams, setQueryParams] = useState({});

  // 数据获取
  const { loading, data: fetchedData, total } = useTableDataFetcher({
    api: customProps.api,
    method: customProps.method || 'post',
    params: queryParams,
    pagination,
    autoRequest: customProps.autoRequest || false,
    responseAfter: customProps.responseAfter,
  });

  // 合并初始数据
  const dataSource = useMemo(() => (initialData.length > 0 ? initialData : fetchedData), [
    initialData,
    fetchedData,
  ]);

  // 分页配置
  const mergedPagination = useMemo(
    () => ({
      ...pagination,
      total,
      onChange: (page, pageSize) => {
        setPagination((prev) => ({ ...prev, current: page, pageSize }));
      },
      onShowSizeChange: (current, size) => {
        setPagination((prev) => ({ ...prev, current: 1, pageSize: size }));
      },
    }),
    [pagination, total]
  );

  // 查询方法
  const query = useCallback((params, shouldResetPagination = true) => {
    setQueryParams(params);
    if (shouldResetPagination) {
      setPagination((prev) => ({
        ...PAGINATION_INIT,
        pageSize: prev.pageSize,
      }));
    }
  }, []);

  // 表格组件
  const tableComponent = useMemo(
    () => (
      <Table
        className={`${className}`}
        scroll={scroll}
        rowKey={(record) => record[rowKey] || JSON.stringify(record)}
        bordered={border}
        dataSource={isShowTableData ? dataSource : []}
        pagination={isShowTableData && dataSource.length > 0 ? mergedPagination : false}
        columns={columns}
        loading={loading}
        {...rest}
      />
    ),
    [dataSource, mergedPagination, loading, columns, rest]
  );

  return [
    tableComponent,
    {
      loading, //加载状态
      dataSource, // 返回数据
      pagination: mergedPagination, //分页
      query, // 查询方法
      getParams: () => queryParams, //请求参数
      setPagination, //设置分页
    },
  ];
};

export default useTable;

useTable 的主要逻辑包括:

  • 分页管理 :通过 useStateuseMemo 管理分页状态。

  • 数据请求 :调用 useTableDataFetcher 获取数据。

  • 查询功能 :提供 query 方法,支持条件查询。

  • 表格渲染 :返回一个 Ant Design 的 Table 组件。

4. 总结

通过 useTable 这个自定义 Hook,我们可以大大简化 Ant Design Table 的使用,减少重复代码,提高开发效率。它封装了分页、数据请求、加载状态等常用功能,使得我们可以更专注于业务逻辑的实现。

如果你也在使用 Ant Design 的 Table 组件,不妨试试这个 useTable Hook,相信它会为你的开发带来便利。如果你有任何改进建议或问题,欢迎在评论区留言讨论!

5. 详细代码

sql 复制代码
import React, { useState, useEffect, useCallback, useMemo, useRef } from 'react';import { Table, message } from 'antd';import request from '@/utils/axios2';/** * * @param  columns  table表头 * @param  scroll  表格参数配置{x,y} * @param  className 样式 * @param  isShowTableData table是否有数据 * @param  initialData 初始数据(方便开发调试使用) * @param  rest table其他配置 * @param  customProps 接口请求获取数据相关参数 具体参数如下 * { api:接口地址、method:请求方式(GET还是POST)、autoRequest:是否自动执行 默认不自动请求、responseAfter:对接口返回数据进行处理 } * 调用举例: *    const [TableComponent, { query }] = useTable({    columns: [{        title: '序号',        align: 'center',        dataIndex: 'code',    },{        title: '姓名',        align: 'center',        dataIndex: 'name',    }],    scroll: { x: 1500 },    customProps: {      api: 'api/getTable',    },  });  查询 query({ name: '小李' })  渲染 <div>{TableComponent}</div> * */// table分页常量定义const PAGINATION_INIT = {  showTotal: (total) => `共 ${total} 条数据`,  pageSize: 10,  current: 1,  showSizeChanger: true,  pageSizeOptions: [10, 20, 50, 100],  total: 0,};// 数据请求状态管理const useTableDataFetcher = ({ api, method, params, pagination, autoRequest, responseAfter }) => {  const [loading, setLoading] = useState(false);  const [data, setData] = useState([]);  const [total, setTotal] = useState(0);  const firstRequestRef = useRef(false); // 判断第一次进入页面  const fetchData = useCallback(async () => {    if (!api) return;    try {      setLoading(true);      const { pageSize, current } = pagination;      const queryParams = {        limit: pageSize,        start: (current - 1) * pageSize,        ...params,      };      const res = await request[method](api, queryParams);      if (res?.success) {        const value = res.value || res;        const processedData =          typeof responseAfter === 'function'            ? await responseAfter(value.rows || [])            : value.rows || [];        setData(processedData);        setTotal(value.results || 0);      } else {        message.error(res?.errorMsg || '数据获取失败');      }    } catch (error) {      if (error.name !== 'AbortError') {        message.error('请求失败,请稍后重试');      }    } finally {      setLoading(false);    }  }, [api, method, params, pagination, responseAfter]);  useEffect(() => {    // 不自动请求 && 是第一次执行    if (!autoRequest && !firstRequestRef.current) {      firstRequestRef.current = true;      return;    } else {      fetchData();    }  }, [fetchData]);  return { loading, data, total };};// 主 Hookconst useTable = (props) => {  const {    scroll = false,    rowKey = 'id',    border = false,    className = '',    columns = [],    pagination: propPagination = {},    customProps = {},    isShowTableData = true,    initialData = [],    ...rest  } = props;  // 分页状态管理  const [pagination, setPagination] = useState({    ...PAGINATION_INIT,    ...propPagination,  });  // 请求参数管理  const [queryParams, setQueryParams] = useState({});  // 数据获取  const { loading, data: fetchedData, total } = useTableDataFetcher({    api: customProps.api,    method: customProps.method || 'post',    params: queryParams,    pagination,    autoRequest: customProps.autoRequest || false,    responseAfter: customProps.responseAfter,  });  // 合并初始数据  const dataSource = useMemo(() => (initialData.length > 0 ? initialData : fetchedData), [    initialData,    fetchedData,  ]);  // 分页配置  const mergedPagination = useMemo(    () => ({      ...pagination,      total,      onChange: (page, pageSize) => {        setPagination((prev) => ({ ...prev, current: page, pageSize }));      },      onShowSizeChange: (current, size) => {        setPagination((prev) => ({ ...prev, current: 1, pageSize: size }));      },    }),    [pagination, total]  );  // 查询方法  const query = useCallback((params, shouldResetPagination = true) => {    setQueryParams(params);    if (shouldResetPagination) {      setPagination((prev) => ({        ...PAGINATION_INIT,        pageSize: prev.pageSize,      }));    }  }, []);  // 表格组件  const tableComponent = useMemo(    () => (      <Table        className={`${className}`}        scroll={scroll}        rowKey={(record) => record[rowKey] || JSON.stringify(record)}        bordered={border}        dataSource={isShowTableData ? dataSource : []}        pagination={isShowTableData && dataSource.length > 0 ? mergedPagination : false}        columns={columns}        loading={loading}        {...rest}      />    ),    [dataSource, mergedPagination, loading, columns, rest]  );  return [    tableComponent,    {      loading, //加载状态      dataSource, // 返回数据      pagination: mergedPagination, //分页      query, // 查询方法      getParams: () => queryParams, //请求参数      setPagination, //设置分页    },  ];};export default useTable;

希望这篇文章对你有所帮助,感谢阅读!

相关推荐
既见君子9 分钟前
透明视频
前端
竹等寒12 分钟前
Go红队开发—web网络编程
开发语言·前端·网络·安全·web安全·golang
lhhbk16 分钟前
angular中下载接口返回文件
前端·javascript·angular·angular.js
YUELEI11820 分钟前
Vue使用ScreenFull插件实现全屏切换
前端·javascript·vue.js
我自纵横20231 小时前
第一章:欢迎来到 HTML 星球!
前端·html
发财哥fdy1 小时前
3.12-2 html
前端·html
ziyu_jia1 小时前
React 组件测试【React Testing Library】
前端·react.js·前端框架
祈澈菇凉1 小时前
如何在 React 中实现错误边界?
前端·react.js·前端框架
撸码到无法自拔1 小时前
❤React-组件的新旧生命周期
前端·javascript·react.js·前端框架·ecmascript
betterangela1 小时前
react基础语法视图层&类组件
前端·javascript·vue.js