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


    最近因为使用列表展示的需求有点多,就想着把列表分页筛选的逻辑抽象一下。看了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 TableControlState<T> extends TableProps<DataRow> {
     13   pagination: PaginationProps;
     14   sorter?: SorterResult<DataRow>;
     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<DataReceive>;
     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<DataRow> = {
     65   pagination: { current: 1, total: 0, pageSize: 10 },
     66   dataSource: [],
     67   loading: true
     68 };
     69 
     70 export type FormTableRet = [
     71   TableControlState<DataRow>,
     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<DataRow[]>([]);
     90   let [tableState, setTableState] = React.useState<TableControlState<DataRow>>(
     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     searchLocalParam
    133   );
    134 
    135   function onSorterChange(
    136     _pagination: PaginationConfig,
    137     _filters: Record<keyof DataRow, string[]>,
    138     _sorter: SorterResult<DataRow>,
    139     _extra: TableCurrentDataSource<DataRow>
    140   ) {
    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     newPage
    161   );
    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<DataRow>>(
    174     defaultTableState
    175   );
    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.pageSize
    204   };
    205 
    206   function getTableApiData() {
    207     if (options.validateParam && !options.validateParam(searchApiParam)) {
    208       return;
    209     }
    210     setState(prevState => {
    211       return Object.assign({}, prevState, {
    212         loading: true
    213       } as TableControlState<any>);
    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.pageSize
    225           },
    226           loading: false
    227         } as TableControlState<any>);
    228       });
    229       // 将表单数据同步到query
    230       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<keyof DataRow, string[]>,
    240     _sorter: SorterResult<DataRow>,
    241     _extra: TableCurrentDataSource<DataRow>
    242   ) {
    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.pagination
    260   );
    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.loading
    274     },
    275     { fetchData: getTableApiData },
    276     state
    277   ] as any;
    278 }
    279 
    280 // 排序,筛选,计算分页数据
    281 function calculatePageData(
    282   state: TableControlState<DataRow>,
    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 * pageSize
    338   );
    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<IProps> = 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     <div className={'alarm-page-content'}>
    18       <Form layout={'inline'}>
    19         <FormItem>
    20           {form.getFieldDecorator('name')(
    21             <Input.Search
    22               style={{ marginLeft: 16,  150 }}
    23               placeholder="名称"
    24             />
    25           )}
    26         </FormItem>
    27         <FormItem>
    28           {form.getFieldDecorator('search')(
    29             <Input.Search
    30               style={{ marginLeft: 16,  150 }}
    31               placeholder="评论"
    32             />
    33           )}
    34         </FormItem>
    35       </Form>
    36       <Table {...tableState} columns={[]} />
    37     </div>
    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<T>(fn, deps: T) {
     4  4   // 使用一个数字信号控制是否渲染,简化 react 的计算,也便于调试
     5  5   let renderRef = useRef<number | any>(0);
     6  6   let depsRef = useRef<T>(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

     

     
  • 相关阅读:
    文件可视化的脚本
    Qt error: invalid application of 'sizeof' to incomplete type 'QStaticAssertFailure<false>'
    libusb(4)在Qt中使用libusb
    USB开发者培训(2)架构、系统、协议和供电
    USB开发者培训(1)技术背景
    你的py文件转换成linux可执行程序
    UOS在启动KVM虚拟机时,发生了如下错误
    远程文件到本地
    linux系统后台运行python脚本
    IEEE Journals, Conferences and Their Abbreviation
  • 原文地址:https://www.cnblogs.com/wanqingying/p/12579819.html
Copyright © 2020-2023  润新知