欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2022-06-22 09:20:54
最近因为使用列表展示的需求有点多,就想着把列表分页筛选的逻辑抽象一下。看了umi的一个useTable的hook,也不能满足业务需要,于是就自己写了一个,支持本地分页筛选和接口分页筛选。 思路就是,筛选的字段都使用form表单控制,然后在hook里面将form和table联合起来。 下面贴出源码 1 ......

最近因为使用列表展示的需求有点多,就想着把列表分页筛选的逻辑抽象一下。看了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 }
下面是业务代码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, width: 150 }}
23               placeholder="名称"
24             />
25           )}
26         </formitem>
27         <formitem>
28           {form.getfielddecorator('search')(
29             <input.search
30               style={{ marginleft: 16, width: 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);
 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 }