当前位置: 首页 > news >正文

列表页常见 hook 封装

本文是深入浅出 ahooks 源码系列文章的第十六篇,这个系列的目标主要有以下几点:

  • 加深对 React hooks 的理解。
  • 学习如何抽象自定义 hooks。构建属于自己的 React hooks 工具库。
  • 培养阅读学习源码的习惯,工具库是一个对源码阅读不错的选择。

列表页常见元素

对于一些后台管理系统,典型的列表页包括筛选表单项、Table表格、Pagination分页这三部分。

针对使用 Antd 的系统,在 ahooks 中主要是通过 useAntdTable 和 usePagination 这两个 hook 来封装。

usePagination

usePagination 基于 useRequest 实现,封装了常见的分页逻辑。

首先通过 useRequest 处理请求,service 约定返回的数据结构为 { total: number, list: Item[] }

其中 useRequest 的 defaultParams 参数第一个参数为 { current: number, pageSize: number }。并根据请求的参数以及返回的 total 值,得出总的页数。

还有 refreshDeps 变化,会重置 current 到第一页「changeCurrent(1)」,并重新发起请求,一般你可以把 pagination 依赖的条件放这里。

const usePagination = <TData extends Data, TParams extends Params>(service: Service<TData, TParams>,options: PaginationOptions<TData, TParams> = {},
) => {const { defaultPageSize = 10, ...rest } = options;// service 返回的数据结构为 { total: number, list: Item[] }const result = useRequest(service, {// service 的第一个参数为 { current: number, pageSize: number }defaultParams: [{ current: 1, pageSize: defaultPageSize }],// refreshDeps 变化,会重置 current 到第一页,并重新发起请求,一般你可以把 pagination 依赖的条件放这里refreshDepsAction: () => {// eslint-disable-next-line @typescript-eslint/no-use-before-definechangeCurrent(1);},...rest,});// 取到相关的请求参数const { current = 1, pageSize = defaultPageSize } = result.params[0] || {};// 获取请求结果,total 代表数据总条数const total = result.data?.total || 0;// 获取到总的页数const totalPage = useMemo(() => Math.ceil(total / pageSize), [pageSize, total]);
} 

重点看下 onChange 方法:

  • 入参分别为当前页数以及当前每一页的最大数量。
  • 根据 total 算出总页数。
  • 获取到所有的参数,执行请求逻辑。
  • 当修改当前页或者当前每一页的最大数量的时候,直接调用 onChange 方法。
// c,代表 current page
// p,代表 page size
const onChange = (c: number, p: number) => {let toCurrent = c <= 0 ? 1 : c;const toPageSize = p <= 0 ? 1 : p;// 根据 total 算出总页数const tempTotalPage = Math.ceil(total / toPageSize);// 假如此时总页面小于当前页面,需要将当前页面赋值为总页数if (toCurrent > tempTotalPage) {toCurrent = Math.max(1, tempTotalPage);}const [oldPaginationParams = {}, ...restParams] = result.params || [];// 重新执行请求result.run(// 留意参数变化,主要是当前页数和每页的总数量发生变化{...oldPaginationParams,current: toCurrent,pageSize: toPageSize,},...restParams,);
};

const changeCurrent = (c: number) => {onChange(c, pageSize);
};

const changePageSize = (p: number) => {onChange(current, p);
}; 

最后返回请求的结果以及 pagination 字段,包含所有分页信息。另外还有操作分页的函数。

return {...result,// 会额外返回 pagination 字段,包含所有分页信息,及操作分页的函数。pagination: {current,pageSize,total,totalPage,onChange: useMemoizedFn(onChange),changeCurrent: useMemoizedFn(changeCurrent),changePageSize: useMemoizedFn(changePageSize),},
} as PaginationResult<TData, TParams>; 

小结:usePagination 默认用法与 useRequest 一致,但内部封装了分页请求相关的逻辑。返回的结果多返回一个 pagination 参数,包含所有分页信息,及操作分页的函数。

缺点就是对 API 请求参数有所限制,比如入参结构必须为 { current: number, pageSize: number },返回结果为 { total: number, list: Item[] }

useAntdTable

useAntdTable 基于 useRequest 实现,封装了常用的 Ant Design Form 与 Ant Design Table 联动逻辑,并且同时支持 antd v3 和 v4。

首先调用 usePagination 处理分页的逻辑。

const useAntdTable = <TData extends Data, TParams extends Params>(service: Service<TData, TParams>,options: AntdTableOptions<TData, TParams> = {},
) => {const {// form 实例form,// 默认表单选项defaultType = 'simple',// 默认参数,第一项为分页数据,第二项为表单数据。[pagination, formData]defaultParams,manual = false,// refreshDeps 变化,会重置 current 到第一页,并重新发起请求。refreshDeps = [],ready = true,...rest} = options;// 对分页的逻辑进行处理// 分页也是对 useRequest 的再封装const result = usePagination<TData, TParams>(service, {manual: true,...rest,});// ...
} 

然后处理列表页筛选 Form 表单的逻辑,这里支持 Antd v3 和 Antd v4 版本。

// 判断是否为 Antd 的第四版本
const isAntdV4 = !!form?.getInternalHooks; 

获取当前表单值,form.getFieldsValue 或者 form.getFieldInstance

// 获取当前的 from 值
const getActivetFieldValues = () => {if (!form) {return {};}// antd 4if (isAntdV4) {return form.getFieldsValue(null, () => true);}// antd 3const allFieldsValue = form.getFieldsValue();const activeFieldsValue = {};Object.keys(allFieldsValue).forEach((key: string) => {if (form.getFieldInstance ? form.getFieldInstance(key) : true) {activeFieldsValue[key] = allFieldsValue[key];}});return activeFieldsValue;
}; 

校验表单逻辑 form.validateFields:

// 校验逻辑
const validateFields = (): Promise<Record<string, any>> => {if (!form) {return Promise.resolve({});}const activeFieldsValue = getActivetFieldValues();const fields = Object.keys(activeFieldsValue);// antd 4// validateFields 直接调用if (isAntdV4) {return (form.validateFields as Antd4ValidateFields)(fields);}// antd 3return new Promise((resolve, reject) => {form.validateFields(fields, (errors, values) => {if (errors) {reject(errors);} else {resolve(values);}});});
}; 

重置表单 form.setFieldsValue

// 重置表单
const restoreForm = () => {if (!form) {return;}// antd v4if (isAntdV4) {return form.setFieldsValue(allFormDataRef.current);}// antd v3const activeFieldsValue = {};Object.keys(allFormDataRef.current).forEach((key) => {if (form.getFieldInstance ? form.getFieldInstance(key) : true) {activeFieldsValue[key] = allFormDataRef.current[key];}});form.setFieldsValue(activeFieldsValue);
}; 

修改表单类型,支持 'simple''advance'。初始化的表单数据可以填写 simple 和 advance 全量的表单数据,开发者可以根据当前激活的类型来设置表单数据。修改 type 的时候会重置 form 表单数据。

const changeType = () => {// 获取当前表单值const activeFieldsValue = getActivetFieldValues();// 修改表单值allFormDataRef.current = {...allFormDataRef.current,...activeFieldsValue,};// 设置表单类型setType((t) => (t === 'simple' ? 'advance' : 'simple'));
};

// 修改 type,则重置 form 表单数据
useUpdateEffect(() => {if (!ready) {return;}restoreForm();
}, [type]); 

_submit 方法:对 form 表单校验后,根据当前 form 表单数据、分页等筛选条件进行对表格数据搜索。

const _submit = (initPagination?: TParams[0]) => {setTimeout(() => {// 先进行校验validateFields().then((values = {}) => {// 分页的逻辑const pagination = initPagination || {pageSize: options.defaultPageSize || 10,...(params?.[0] || {}),current: 1,};// 假如没有 form,则直接根据分页的逻辑进行请求if (!form) {// @ts-ignorerun(pagination);return;}// 获取到当前所有 form 的 Data 参数// record all form dataallFormDataRef.current = {...allFormDataRef.current,...values,};// @ts-ignorerun(pagination, values, {allFormData: allFormDataRef.current,type,});}).catch((err) => err);});
}; 

另外当表格触发 onChange 方法的时候,也会进行请求:

// Table 组件的 onChange 事件
const onTableChange = (pagination: any, filters: any, sorter: any) => {const [oldPaginationParams, ...restParams] = params || [];run(// @ts-ignore{...oldPaginationParams,current: pagination.current,pageSize: pagination.pageSize,filters,sorter,},...restParams,);
}; 

初始化的时候,会根据当前是否有缓存的数据,有则根据缓存的数据执行请求逻辑。否则,通过 manualready 判断是否需要进行重置表单后执行请求逻辑。

// 初始化逻辑
// init
useEffect(() => {// if has cache, use cached params. ignore manual and ready.// params.length > 0,则说明有缓存if (params.length > 0) {// 使用缓存的数据allFormDataRef.current = cacheFormTableData?.allFormData || {};// 重置表单后执行请求restoreForm();// @ts-ignorerun(...params);return;}// 非手动并且已经 ready,则执行 _submitif (!manual && ready) {allFormDataRef.current = defaultParams?.[1] || {};restoreForm();_submit(defaultParams?.[0]);}
}, []); 

最后,将请求返回的数据通过 dataSource、 pagination、loading 透传回给到 Table 组件,实现 Table 的数据以及状态的展示。以及将对 Form 表单的一些操作方法暴露给开发者。

return {...result,// Table 组件需要的数据,直接透传给 Table 组件即可tableProps: {dataSource: result.data?.list || defaultDataSourceRef.current,loading: result.loading,onChange: useMemoizedFn(onTableChange),pagination: {current: result.pagination.current,pageSize: result.pagination.pageSize,total: result.pagination.total,},},search: {// 提交表单submit: useMemoizedFn(submit),// 当前表单类型, simple | advancetype,// 切换表单类型changeType: useMemoizedFn(changeType),// 重置当前表单reset: useMemoizedFn(reset),},
} as AntdTableResult<TData, TParams>; 

相关文章:

  • 集合_HashSet(HashMap)扩容机制源码简析
  • Spring注解@Qualifier的详细用法你知道几种「扩展点实战系列」- 第444篇
  • uni-app 微信小程序中关于 map 地图使用案例分享
  • 工业级成熟航运港口人工智能产品全球前三船公司及港口码头落地,中国上海人工智能独角兽中集飞瞳全球应用最广规模最大最先进港航AI企业
  • CSS基础篇---02选择器进阶、背景样式、显示模式
  • 【C语言】自定义类型 —— 结构体
  • 千万级用户ms级抽奖N名设计方案
  • 2022第五空间WEBMISC
  • 说几句得罪人的大实话
  • Spark 优化 (二) --------- Spark 数据倾斜
  • 第01篇:系统化学习, 搞定Spring容器管理
  • 【Android】-- Intent(显式和隐式Intent)
  • 【HashMap】HashMap的6种遍历方法
  • 网络中其他重要技术与协议(DNS系统,ICMP协议,NAT技术与代理服务器)
  • [仅需1步]企业微信群机器人[0基础接入][java]
  • 收藏网友的 源程序下载网
  • [Vue CLI 3] 配置解析之 css.extract
  • 【跃迁之路】【641天】程序员高效学习方法论探索系列(实验阶段398-2018.11.14)...
  • 78. Subsets
  • Apache的基本使用
  • Laravel Mix运行时关于es2015报错解决方案
  • SpiderData 2019年2月16日 DApp数据排行榜
  • Vue组件定义
  • Webpack 4 学习01(基础配置)
  • 浅谈Golang中select的用法
  • 使用 Xcode 的 Target 区分开发和生产环境
  • 自动记录MySQL慢查询快照脚本
  • # 安徽锐锋科技IDMS系统简介
  • (2022版)一套教程搞定k8s安装到实战 | RBAC
  • (C语言)输入一个序列,判断是否为奇偶交叉数
  • (亲测成功)在centos7.5上安装kvm,通过VNC远程连接并创建多台ubuntu虚拟机(ubuntu server版本)...
  • (转)甲方乙方——赵民谈找工作
  • (转)微软牛津计划介绍——屌爆了的自然数据处理解决方案(人脸/语音识别,计算机视觉与语言理解)...
  • (转载)微软数据挖掘算法:Microsoft 时序算法(5)
  • ..回顾17,展望18
  • .aanva
  • .net CHARTING图表控件下载地址
  • .net core MVC 通过 Filters 过滤器拦截请求及响应内容
  • .NET MVC第三章、三种传值方式
  • .NET 发展历程
  • .net 受管制代码
  • .NET 中 GetHashCode 的哈希值有多大概率会相同(哈希碰撞)
  • .NET框架
  • .NET序列化 serializable,反序列化
  • @modelattribute注解用postman测试怎么传参_接口测试之问题挖掘
  • [ Linux ] git工具的基本使用(仓库的构建,提交)
  • [<事务专题>]
  • [20170705]diff比较执行结果的内容.txt
  • [2018][note]用于超快偏振开关和动态光束分裂的all-optical有源THz超表——
  • [hdu1561] The more, The Better 【树形DP】
  • [ICCV2017]Neural Person Search Machines
  • [Java][方法引用]构造方法的引用事例分析
  • [JS]数据类型
  • [linux c]linux do_div() 函数用法
  • [Linux]创建新用户并授予root权限