React封装强业务hook的一个例子
程序员文章站
2022-03-25 16:57:14
最近因为使用列表展示的需求有点多,就想着把列表分页筛选的逻辑抽象一下。看了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 }