关于我们

质量为本、客户为根、勇于拼搏、务实创新

< 返回新闻公共列表

React封装强业务hook的一个例子

发布时间:2020-03-28 00:00:00

最近因为使用列表展示的需求有点多,就想着把列表分页筛选的逻辑抽象一下。看了umi的一个useTable的hook,也不能满足业务需要,于是就自己写了一个,支持本地分页筛选和接口分页筛选。

思路就是,筛选的字段都使用form表单控制,然后在hook里面将form和table联合起来。

  下面贴出源码


  1 import { TableProps, PaginationProps } from '@slardar/antd';  2 import React, { useEffect } from 'react';  3 import {  4   PaginationConfig,  5   SorterResult,  6   TableCurrentDataSource  7 } from 'antd/lib/table';  8 import { useDeepCompareEffect } from '@byted-woody/slardar';  9  10 export type WrappedFormUtils = any; 11  12 export interface TableControlStateextends TableProps { 13   pagination: PaginationProps; 14   sorter?: SorterResult; 15   loading?: boolean; 16 } 17  18 // 搜索参数描述 19 export interface SearchDescribe { 20   // 字段名 21   fieldName: string; 22   iniValue?: any; 23   // 输入值解析,比如日期输入解析 24   decodeFn?: any; 25   // 自定义搜索函数 26   searchFn?: (record: DataRow, desc: SearchDescribe) => boolean; 27   // 解析后的值 28   searchValue?: any; 29   // 调用接口或者只在本地过滤 30   searchMod?: 'api' | 'local'; 31   // 搜索的字段,默认只搜索当前字段 32   searchFields?: string[]; 33 } 34  35 export interface DataReceive { 36   pageSize?: number; 37   pageIndex?: number; 38   pageTotal?: number; 39   pageData: any[]; 40 } 41  42 export type DataRow = { [key: string]: any }; 43 export type DataApiGet = ( 44   apiParams, 45   pagination?: { pageIndex?: number; pageSize?: number } 46 ) => Promise; 47  48 export interface FormTableReq { 49   form: WrappedFormUtils; 50   getDataApi: DataApiGet; 51   getDataParam?: { [key: string]: any }; 52   // 表单的字段解析 53   includeFormFields?: (SearchDescribe | string)[]; 54   // 本地分页 55   localPagination?: boolean; 56   // 本地搜索 57   localSearch?: boolean; 58   // 本地分页+搜索 59   local?: boolean; 60   afterFetchData?: (v: any) => void; 61   validateParam?: (param: any) => boolean; 62 } 63  64 const defaultTableState: TableControlState= { 65   pagination: { current: 1, total: 0, pageSize: 10 }, 66   dataSource: [], 67   loading: true 68 }; 69  70 export type FormTableRet = [ 71   TableControlState, 72   { fetchData: () => void } 73 ]; 74  75 export function useFormTable(options: FormTableReq): FormTableRet { 76   if (options.local) { 77     options?.includeFormFields?.forEach(d => (d.searchMod = 'local')); 78     return useFormTableLocal(options); 79   } else { 80     return useFormTableDB(options); 81   } 82 } 83  84 // 本地分页筛选版本 85 export function useFormTableLocal(options: FormTableReq): FormTableRet { 86   let { form, getDataApi, includeFormFields } = options; 87   let currentFormValue = form.getFieldsValue(); 88   // 缓存数据 89   let cacheDataListRef = React.useRef([]); 90   let [tableState, setTableState] = React.useState<TableControlState>( 91     defaultTableState 92   ); 93   let searchApiParam = {}; 94   let searchLocalParam: SearchDescribe[] = []; 95   if (Array.isArray(includeFormFields)) { 96     includeFormFields?.forEach(describe => { 97       if (typeof describe === 'string') { 98         let value = currentFormValue[describe]; 99         searchApiParam[describe] = value;100       } else {101         let value = currentFormValue[describe.fieldName];102         if (describe.decodeFn) {103           value = describe.decodeFn(value);104         }105         if (describe.searchMod === 'api') {106           searchApiParam[describe.fieldName] = value;107         } else {108           searchLocalParam.push(109             Object.assign({ searchValue: value }, describe)110           );111         }112       }113     });114   } else {115     searchApiParam = currentFormValue;116   }117 118   function getTableApiData() {119     getDataApi(searchApiParam).then(data => {120       cacheDataListRef.current = data.pageData;121       setTableState(prevState => {122         return Object.assign({}, prevState, { dataSource: [] });123       });124     });125   }126 127   useEffect(getTableApiData, []);128 129   let { data, total } = calculatePageData(130     tableState,131     cacheDataListRef.current,132     searchLocalParam133   );134 135   function onSorterChange(136     _pagination: PaginationConfig,137     _filters: Record,138     _sorter: SorterResult,139     _extra: TableCurrentDataSource140   ) {141     setTableState(prevState => {142       return Object.assign({}, prevState, { sorter: _sorter });143     });144   }145 146   let newPage: PaginationProps = {147     total: total,148     onChange: (page, pageSize) => {149       setTableState(prevState => {150         prevState.pagination.pageSize = pageSize;151         prevState.pagination.current = page;152         return Object.assign({}, prevState);153       });154     }155   };156 157   let finalPagination: PaginationProps = Object.assign(158     {},159     tableState.pagination,160     newPage161   );162 163   return [164     { pagination: finalPagination, dataSource: data, onChange: onSorterChange },165     { fetchData: getTableApiData }166   ];167 }168 169 // 接口分页筛选版本 待完善170 export function useFormTableDB(options: FormTableReq): FormTableRet {171   let { form, getDataApi, includeFormFields } = options;172   let currentFormValue = form.getFieldsValue();173   let [state, setState] = React.useState<TableControlState>(174     defaultTableState175   );176   let searchApiParam: { [key: string]: any } = {};177   let onceRef = React.useRef(false);178   // 计算接口参数179   if (Array.isArray(includeFormFields)) {180     includeFormFields?.forEach(describe => {181       if (typeof describe === 'string') {182         let value = currentFormValue[describe];183         searchApiParam[describe] = value;184       } else {185         let value = currentFormValue[describe.fieldName];186         if (!onceRef.current && describe.iniValue) {187           value = describe.iniValue;188         }189         if (describe.decodeFn) {190           value = describe.decodeFn(value);191           Object.assign(searchApiParam, value);192         } else {193           searchApiParam[describe.fieldName] = value;194         }195       }196     });197   } else {198     searchApiParam = currentFormValue;199   }200   Object.assign(searchApiParam, options.getDataParam);201   const pageParam = {202     pageIndex: state.pagination.current,203     pageSize: state.pagination.pageSize204   };205 206   function getTableApiData() {207     if (options.validateParam && !options.validateParam(searchApiParam)) {208       return;209     }210     setState(prevState => {211       return Object.assign({}, prevState, {212         loading: true213       } as TableControlState);214     });215     getDataApi(searchApiParam, pageParam).then(data => {216       const { pageData, pageTotal } = data;217       onceRef.current = true;218       setState(prevState => {219         return Object.assign({}, prevState, {220           dataSource: pageData,221           pagination: {222             current: pageParam.pageIndex,223             total: pageTotal || 0,224             pageSize: pageParam.pageSize225           },226           loading: false227         } as TableControlState);228       });229       // 将表单数据同步到query230       if (options.afterFetchData) {231         options.afterFetchData(currentFormValue);232       }233     });234   }235   useDeepCompareEffect(getTableApiData, [searchApiParam, pageParam]);236 237   function onSorterChange(238     _pagination: PaginationConfig,239     _filters: Record,240     _sorter: SorterResult,241     _extra: TableCurrentDataSource242   ) {243     setState(prevState => {244       return Object.assign({}, prevState, { sorter: _sorter });245     });246   }247 248   let finalPagination: PaginationProps = Object.assign(249     {250       total: state.pagination.total,251       onChange: (page, pageSize) => {252         setState(prevState => {253           prevState.pagination.pageSize = pageSize;254           prevState.pagination.current = page;255           return Object.assign({}, prevState);256         });257       }258     },259     state.pagination260   );261   let dataSource = state.dataSource;262   if (options.localPagination) {263     let { data, total } = calculatePageData(state, state.dataSource as any, []);264     finalPagination.total = total;265     dataSource = data;266   }267 268   return [269     {270       pagination: finalPagination,271       dataSource: dataSource,272       onChange: onSorterChange,273       loading: state.loading274     },275     { fetchData: getTableApiData },276     state277   ] as any;278 }279 280 // 排序,筛选,计算分页数据281 function calculatePageData(282   state: TableControlState,283   dataList: DataRow[],284   param: SearchDescribe[]285 ) {286   let { pagination, sorter } = state;287   let { current = 1, pageSize = 10 } = pagination;288   let copyDataList = Array.from(dataList);289   // 排序290   if (sorter?.column) {291     let order = sorter.order;292     let sortField = sorter.columnKey;293     copyDataList = copyDataList.sort((a, b) => {294       if (order === 'ascend') {295         return a[sortField] - b[sortField];296       } else {297         return b[sortField] - a[sortField];298       }299     });300   }301   // 筛选302   if (Array.isArray(param) && param.length > 0) {303     copyDataList = copyDataList.filter(function filter(v) {304       return param.every(desc => {305         let { fieldName, searchValue, searchFields, searchFn } = desc;306         let fieldValue = v[fieldName];307         let searchString = searchValue;308         if (!searchString) {309           return true;310         }311         if (searchFn) {312           return searchFn(v, desc);313         }314         if (315           typeof fieldValue !== 'string' ||316           typeof searchString !== 'string'317         ) {318           return true;319         }320         if (searchFields?.length) {321           return searchFields?.some(fieldName => {322             let value = v[fieldName];323             if (typeof value === 'string') {324               value.includes(searchString);325             }326             return false;327           });328         } else {329           return fieldValue.includes(searchString);330         }331       });332     });333   }334   // 分页335   let displayData = copyDataList.slice(336     (current - 1) * pageSize,337     current * pageSize338   );339   // 默认空数据的展示340   displayData.forEach(d => {341     Object.entries(d).forEach(([k, v]) => {342       if (v !== 0 && !v) {343         d[k] = '---';344       }345     });346     return d;347   });348   return { data: displayData, total: copyDataList.length };349 }
useFormTable.tsx
下面是业务代码demo
 1 import React, { FC } from 'react'; 2 import { Form, FormComponentProps, Input, Table } from '@slardar/antd'; 3 import { useFormTable } from '@slardar/common-modules'; 4  5 const FormItem = Form.Item; 6  7 interface IProps extends FormComponentProps {} 8 const DemoComponent: FC= function(props) { 9   const form = props.form;10   let [tableState] = useFormTable({11     form: props.form,12     getDataApi: () => Promise.resolve([] as any),13     includeFormFields: ['name', 'search']14   });15 16   return (17     18       19         20           {form.getFieldDecorator('name')(21             <Input.Search22               style={{ marginLeft: 16, width: 150 }}23               placeholder="名称"24             />25           )}26         27         28           {form.getFieldDecorator('search')(29             <Input.Search30               style={{ marginLeft: 16, width: 150 }}31               placeholder="评论"32             />33           )}34         35       36       37     38   );39 };40 41 export const Demo = Form.create()(DemoComponent);
Demo.tsx
 1  1 import { useRef, useEffect } from 'react'; 2  2 import _ from 'lodash'; 3  3 export function useDeepCompareEffect(fn, deps: T) { 4  4   // 使用一个数字信号控制是否渲染,简化 react 的计算,也便于调试 5  5   let renderRef = useRef(0); 6  6   let depsRef = useRef(deps); 7  7   if (!_.isEqual(deps, depsRef.current)) { 8  8     renderRef.current++; 9  9   }10 10   depsRef.current = deps;11 11   return useEffect(fn, [renderRef.current]);12 12 }
useDeepCompareEffect.ts

 

 

 

/template/Home/Zkeys/PC/Static