在前端开发中,表格(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
的主要逻辑包括:
-
分页管理 :通过
useState
和useMemo
管理分页状态。 -
数据请求 :调用
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;
希望这篇文章对你有所帮助,感谢阅读!