在开发中,我们常常需要处理表格数据的请求、缓存、导出等功能。为了提高代码的复用性和简洁性,我们可以将这些逻辑封装到一个自定义 Hook 中。本文将介绍一个基于 Ant Design Pro Table 的表格请求封装 Hook —— useProTableRequest,并深入讲解其工作原理和如何在项目中使用。
功能概述
useProTableRequest 是一个用于处理表格数据请求的自定义 Hook,主要包括以下功能:
• 数据请求:从服务器获取数据,并支持分页、排序和筛选功能。
• 数据缓存:缓存请求的参数和数据,方便在用户返回该页面时恢复表格状态(如分页、筛选等)。
• 参数格式化:可以自定义格式化请求参数和返回的数据
代码code
import { ActionType, ProFormInstance, RequestData } from '@ant-design/pro-components'
import { SortOrder } from 'antd/lib/table/interface'
import React, { useCallback, useEffect, useLayoutEffect, useRef } from 'react'
type Params<U> = U & {
pageSize?: number
current?: number
keyword?: string
}
type Sort = Record<string, SortOrder>
type Filter = Record<string, React.ReactText[] | null>
type Fn<U, T> = (
params: any,
sort: Sort,
filter: Filter
) => Promise<{ code?: number; msg?: string; data?: { list?: T[]; total?: number } }>
export interface IUseProTableRequestOption<T, U = T> {
/** 需要缓存的目标链接地址
* 目的: 缓存列表页的请求参数,例如分页、排序、搜索等参数,方便详情回列表页时自动恢复
* @example [routeNames.userManagementUserListUserDetail]
*/
cacheUrls?: string[]
/**
* 格式化数据
* 你可以对返回的数据做一些处理
*/
dataFormat?: (data: T[]) => U[]
/**
* 格式化参数
*
* 前置处理请求参数。如果你需要传递给导出时。这会很有用
* @param params 将要传递给接口的参数
*/
paramsFormat?(params: any): any
}
/**
* antd pro table请求封装钩子
* @param fn
* @param option
* @returns
*/
export default function useProTableRequest<T, U extends Record<string, any> = {}>(
fn: Fn<U, T>,
option: IUseProTableRequestOption<T> = {}
) {
const { dataFormat, cacheUrls } = option
// Table action 的引用,便于自定义触发
const actionRef = useRef<ActionType>()
const formRef = useRef<ProFormInstance>()
// 缓存请求参数
const requestParams = useRef<Record<string, any>>({})
// 数据缓存参数
const dataSourceRef = useRef<T[]>([])
/** 缓存参数 */
const _cacheData = useRef<any>(undefined)
/** 缓存key */
const cacheKey = genCacheKey()
/** 当前是否需要缓存 */
const needCache = !!cacheUrls?.length
// 表格请求
const tableRequst = useCallback(async (params: Params<U>, sort: Sort, filter: Filter) => {
const { current, ...rest } = params as Record<string, any>
const newParams: any = { ...rest, pageNum: current ?? params?.pageNum ?? 1 }
// 重置数据
let total = 0
let data: T[] = []
requestParams.current = option.paramsFormat ? option.paramsFormat(newParams) : newParams
try {
// 参数长度过长不处理
if (JSON.stringify(requestParams.current).length < 1000) {
const res = await fn(requestParams.current, sort, filter)
// 如果当前列表为空并且pageNum不为1.则重新发起请求
if (!res.data?.list?.length && requestParams.current.pageNum !== 1) {
setTimeout(() => {
actionRef.current?.reload(true)
})
}
const { list = [] as T[] } = res.data || {}
total = res.data?.total || 0
data = dataFormat ? dataFormat(list) : list
// 缓存数据
_cacheData.current = { ...requestParams.current }
dataSourceRef.current = data
}
} catch (error) {}
// }
return { data, success: true, total } as Partial<RequestData<T>>
}, [])
/** 恢复缓存 */
const restoreCache = () => {
if (needCache) {
const cacheString = window.sessionStorage.getItem(cacheKey)
if (cacheString) {
try {
const cacheParams = JSON.parse(cacheString)
/** 恢复请求参数 */
_cacheData.current = cacheParams
/** 恢复搜索表单 */
formRef.current?.setFieldsValue(cacheParams)
/** 恢复分页信息 */
actionRef.current?.setPageInfo?.({
current: cacheParams?.current ?? cacheParams?.pageNum ?? 1,
pageSize: cacheParams?.pageSize
})
} catch (error) {
} finally {
window.sessionStorage.removeItem(cacheKey) // 清除缓存数据
}
}
}
}
useLayoutEffect(() => {
restoreCache()
}, [])
/**
* 表格数据缓存处理
*/
useEffect(() => {
return () => {
if (!needCache) {
return
}
// 组件卸载时将数据缓存至sessionStorage
const to = pick(window.location, ['hash', 'href', 'protocol', 'port', 'search', 'pathname', 'hostname'])
const toCache = cacheUrls?.some((url) => to?.href?.includes(url))
if (toCache) {
window.sessionStorage.setItem(cacheKey, JSON.stringify(_cacheData.current))
}
}
}, [])
return {
formRef,
actionRef,
/**
* 表格请求
*/
request: tableRequst,
/**
* 表格query参数
*/
params: requestParams,
/**
* 表格数据缓存
*/
dataSource: dataSourceRef
}
}
/**
* 从对象中选择指定的属性
* @param obj
* @param keys
* @returns
*/
function pick<T extends Object, K extends keyof T>(obj: T, keys: K[]): Pick<T, K> {
const result = {} as Pick<T, K>
keys.forEach((key) => {
if (key in obj) {
result[key] = obj[key]
}
})
return result
}
/**
* 生成缓存键值
* @param key key
* @returns
*/
function genCacheKey(key?: string | ((ok: string) => string)) {
const hostKey = encodeURIComponent(window.location.href.replace(/http(s)?:\/\//, ''))
if (typeof key === 'function') {
return key(hostKey)
}
return key || hostKey
}
简单实例
const { request, actionRef, formRef } = useProTableRequest(api['/admin/mall/banner/queryList_GET'])
