Ant Design ProTable 的 request 约定很简单:接收分页、表单、排序和筛选参数,返回 data、success 与 total。真正值得封装的是项目后端协议的适配,而不是在 Hook 内再造一套缓存或吞掉所有错误。
先定义稳定的边界
下面不从 Ant Design 的内部文件路径导入 SortOrder,而是只依赖 ProTable 所需的结构。这样可以降低组件库目录调整带来的升级成本。
import { useCallback } from 'react'
type SortValue = 'ascend' | 'descend' | null
type Sorter = Record<string, SortValue>
type Filter = Record<
string,
Array<string | number | boolean> | null
>
type TableParams = {
current?: number
pageSize?: number
[key: string]: unknown
}
type ProTableResult<T> = {
data: T[]
success: boolean
total: number
}
type ProTableRequest<T, P extends TableParams> = (
params: P,
sort: Sorter,
filter: Filter
) => Promise<ProTableResult<T>>
interface PageResult<T> {
records: T[]
total: number
}
interface UseProTableRequestOptions<T, P extends TableParams> {
request: (
params: P,
sort: Sorter,
filter: Filter
) => Promise<PageResult<T>>
onError?: (error: unknown) => void
}
export function useProTableRequest<
T,
P extends TableParams = TableParams
>(
options: UseProTableRequestOptions<T, P>
): ProTableRequest<T, P> {
const { request, onError } = options
return useCallback(
async (params, sort, filter) => {
try {
const result = await request(params, sort, filter)
return {
data: result.records,
total: result.total,
success: true
}
} catch (error) {
onError?.(error)
return {
data: [],
total: 0,
success: false
}
}
},
[request, onError]
)
}
这个 Hook 只做两件事:
- 把项目接口的
records/total转成 ProTable 的返回结构。 - 统一失败结果,并把错误交给页面或监控处理。
使用示例
import { useCallback } from 'react'
import { ProTable } from '@ant-design/pro-components'
import { message } from 'antd'
export function UserTable() {
const queryUsers = useCallback(
async (
params: TableParams,
sort: Sorter,
filter: Filter
) => {
const response = await userService.page({
page: params.current ?? 1,
pageSize: params.pageSize ?? 20,
keyword: params.keyword,
sort,
filter
})
return {
records: response.records,
total: response.total
}
},
[]
)
const request = useProTableRequest({
request: queryUsers,
onError: () => {
message.error('用户列表加载失败')
}
})
return (
<ProTable<User>
rowKey="id"
columns={columns}
request={request}
pagination={{ defaultPageSize: 20 }}
/>
)
}
传入 Hook 的 request 和 onError 应保持稳定。若直接在 JSX 每次创建新函数,useCallback 无法带来稳定引用,表格还可能因依赖变化产生额外请求。
排序参数需要显式翻译
前端通常使用 ascend/descend,后端可能使用 asc/desc,字段名也未必相同。不要把整个 sort 对象原样透传给服务端:
function toOrderBy(sort: Sorter) {
const allowedFields = new Set(['createdAt', 'name'])
return Object.entries(sort)
.filter(
(entry): entry is [string, 'ascend' | 'descend'] =>
allowedFields.has(entry[0]) &&
entry[1] !== null
)
.map(([field, order]) => ({
field,
direction: order === 'ascend' ? 'asc' : 'desc'
}))
}
字段白名单不仅是类型问题,也能避免把任意客户端字段拼进数据库排序语句。
不建议放进这个 Hook 的能力
- 请求缓存: ProTable 自己管理请求触发;跨页面缓存应由数据层负责。
- 自动重置页码: 表单提交、重置和
actionRef.current?.reloadAndRest?.()的语义应由页面明确选择。 - 静默成功: 捕获异常后仍返回
success: true会让空数据和失败无法区分。 - 私有类型导入:
antd/es/...下的实现路径不是稳定的公共契约。 - 业务参数拼装: 门店、租户和权限范围应在业务请求函数中清晰出现。
ProTable 官方文档:ProTable。升级组件库时,应以项目锁定版本对应的 API 为准。