React封裝強業務hook的一個例子

来源:https://www.cnblogs.com/wanqingying/archive/2020/03/27/12579819.html
-Advertisement-
Play Games

最近因為使用列表展示的需求有點多,就想著把列表分頁篩選的邏輯抽象一下。看了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 }
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, 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);
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

 

 

 

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • ,超文本標記語言,不是一種編程語言,而是一種標記語言 _思想:網頁中有很多數據,不同的數據可能需要不同的顯示效果,一個標簽相當於一個容器,想要修改容器內數據的樣式,只需要改變容器的屬性值,就可以實現容器內數據樣式的變化。_ 語言結構介紹: html學習 塊級標簽 顯示為"塊"狀,瀏覽器會在其前後顯示 ...
  • Ajax的狀態:xhr.readyState ·UNSENT 0 :創建完XHR預設就是0 ·OPENED 1 :已經完成OPEN操作 ·HEADERS_RECEIVED 2 :伺服器已經把響應頭信息返回了 ·LOADING 3 :響應主體正在返回中 ·DONE 4 :響應主體已經返回 Http的狀 ...
  • HTML結構如下: CCS結構如下: 頁面效果圖如下: 現在我們可以看到在子元素中明明設置了向右50px和向下50px,可頁面顯示的效果卻只有向右移動的沒向下移動的。 通常來說,margin是設置元素的外邊距,正常情況下設置margin值時應該是父元素相對於瀏覽器定位,子元素相對於父元素定位;而現在 ...
  • 類: 降低維護成本、使代碼高度復用、擴充方便靈活 OOP 面向對象開發 核心:封裝 類->工廠->對象 ES6中的類 //車類 class Car{ //構造函數 constructor(){ console.log("開始造車"); } } //實例化,類->對象 let c=new Car(); ...
  • 之前怎麼用回調解決非同步的問題: function f(callback){ setTimeout(function(){ callback && callback(); }); } f(function(){ console.log(1); f(function(){ console.log(2); ...
  • 前端學習路徑 1.WEB前端快速入門 在本階段,我們需要掌握 HTML 與 CSS 基礎,當然,也包含 H5 和 C3 的新特性。這個部分內容非常簡單,而且非常容易掌握。相信你也更願意學習這個部分,畢竟他可以讓你最直觀的感受到前端的魅力。為了鍛煉大家寫代碼,可以根據你喜歡的站點去實現效果。這一階段是 ...
  • 首先呈現效果圖 1.我的是通過element-ui實現的;可自由選擇UI 2.話不多說,直接上代碼,結構非常簡單 1 <el-form ref="form" :model="form" label-width="100px"> 2 <el-form-item label=""> 3 一:vue實現城 ...
  • 先說說react原版的useEffect使用起來不便的地方 useEffect( function() { // effect操作 }, ['a', 'b', { name: 'c' }] ); 這裡的effect每次更新都會執行,因為第三個參數一直是不等的,{name: 'c'} !== {nam ...
一周排行
    -Advertisement-
    Play Games
  • C#TMS系統代碼-基礎頁面BaseCity學習 本人純新手,剛進公司跟領導報道,我說我是java全棧,他問我會不會C#,我說大學學過,他說這個TMS系統就給你來管了。外包已經把代碼給我了,這幾天先把增刪改查的代碼背一下,說不定後面就要趕鴨子上架了 Service頁面 //using => impo ...
  • 委托與事件 委托 委托的定義 委托是C#中的一種類型,用於存儲對方法的引用。它允許將方法作為參數傳遞給其他方法,實現回調、事件處理和動態調用等功能。通俗來講,就是委托包含方法的記憶體地址,方法匹配與委托相同的簽名,因此通過使用正確的參數類型來調用方法。 委托的特性 引用方法:委托允許存儲對方法的引用, ...
  • 前言 這幾天閑來沒事看看ABP vNext的文檔和源碼,關於關於依賴註入(屬性註入)這塊兒產生了興趣。 我們都知道。Volo.ABP 依賴註入容器使用了第三方組件Autofac實現的。有三種註入方式,構造函數註入和方法註入和屬性註入。 ABP的屬性註入原則參考如下: 這時候我就開始疑惑了,因為我知道 ...
  • C#TMS系統代碼-業務頁面ShippingNotice學習 學一個業務頁面,ok,領導開完會就被裁掉了,很突然啊,他收拾東西的時候我還以為他要旅游提前請假了,還在尋思為什麼回家連自己買的幾箱飲料都要叫跑腿帶走,怕被偷嗎?還好我在他開會之前拿了兩瓶芬達 感覺感覺前面的BaseCity差不太多,這邊的 ...
  • 概述:在C#中,通過`Expression`類、`AndAlso`和`OrElse`方法可組合兩個`Expression<Func<T, bool>>`,實現多條件動態查詢。通過創建表達式樹,可輕鬆構建複雜的查詢條件。 在C#中,可以使用AndAlso和OrElse方法組合兩個Expression< ...
  • 閑來無聊在我的Biwen.QuickApi中實現一下極簡的事件匯流排,其實代碼還是蠻簡單的,對於初學者可能有些幫助 就貼出來,有什麼不足的地方也歡迎板磚交流~ 首先定義一個事件約定的空介面 public interface IEvent{} 然後定義事件訂閱者介面 public interface I ...
  • 1. 案例 成某三甲醫預約系統, 該項目在2024年初進行上線測試,在正常運行了兩天後,業務系統報錯:The connection pool has been exhausted, either raise MaxPoolSize (currently 800) or Timeout (curren ...
  • 背景 我們有些工具在 Web 版中已經有了很好的實踐,而在 WPF 中重新開發也是一種費時費力的操作,那麼直接集成則是最省事省力的方法了。 思路解釋 為什麼要使用 WPF?莫問為什麼,老 C# 開發的堅持,另外因為 Windows 上已經裝了 Webview2/edge 整體打包比 electron ...
  • EDP是一套集組織架構,許可權框架【功能許可權,操作許可權,數據訪問許可權,WebApi許可權】,自動化日誌,動態Interface,WebApi管理等基礎功能於一體的,基於.net的企業應用開發框架。通過友好的編碼方式實現數據行、列許可權的管控。 ...
  • .Net8.0 Blazor Hybird 桌面端 (WPF/Winform) 實測可以完整運行在 win7sp1/win10/win11. 如果用其他工具打包,還可以運行在mac/linux下, 傳送門BlazorHybrid 發佈為無依賴包方式 安裝 WebView2Runtime 1.57 M ...