列表頁常見hook封裝實例
引言
本文是深入淺出 ahooks 源碼系列文章,這個系列的目標主要有以下幾點:
- 加深對 React hooks 的理解。
- 學習如何抽象自定義 hooks。構(gòu)建屬于自己的 React hooks 工具庫。
- 培養(yǎng)閱讀學習源碼的習慣,工具庫是一個對源碼閱讀不錯的選擇。
列表頁常見元素
對于一些后臺管理系統(tǒng),典型的列表頁包括篩選表單項、Table表格、Pagination分頁這三部分。
針對使用 Antd 的系統(tǒng),在 ahooks 中主要是通過 useAntdTable 和 usePagination 這兩個 hook 來封裝。
usePagination
usePagination 基于 useRequest 實現(xiàn),封裝了常見的分頁邏輯。
首先通過 useRequest 處理請求,service 約定返回的數(shù)據(jù)結(jié)構(gòu)為 { total: number, list: Item[] }
。
其中 useRequest 的 defaultParams 參數(shù)第一個參數(shù)為 { current: number, pageSize: number }
。并根據(jù)請求的參數(shù)以及返回的 total 值,得出總的頁數(shù)。
還有 refreshDeps 變化,會重置 current 到第一頁「changeCurrent(1)
」,并重新發(fā)起請求,一般你可以把 pagination 依賴的條件放這里。
const usePagination = <TData extends Data, TParams extends Params>( service: Service<TData, TParams>, options: PaginationOptions<TData, TParams> = {}, ) => { const { defaultPageSize = 10, ...rest } = options; // service 返回的數(shù)據(jù)結(jié)構(gòu)為 { total: number, list: Item[] } const result = useRequest(service, { // service 的第一個參數(shù)為 { current: number, pageSize: number } defaultParams: [{ current: 1, pageSize: defaultPageSize }], // refreshDeps 變化,會重置 current 到第一頁,并重新發(fā)起請求,一般你可以把 pagination 依賴的條件放這里 refreshDepsAction: () => { // eslint-disable-next-line @typescript-eslint/no-use-before-define changeCurrent(1); }, ...rest, }); // 取到相關的請求參數(shù) const { current = 1, pageSize = defaultPageSize } = result.params[0] || {}; // 獲取請求結(jié)果,total 代表數(shù)據(jù)總條數(shù) const total = result.data?.total || 0; // 獲取到總的頁數(shù) const totalPage = useMemo(() => Math.ceil(total / pageSize), [pageSize, total]); }
重點看下 onChange 方法:
- 入?yún)⒎謩e為當前頁數(shù)以及當前每一頁的最大數(shù)量。
- 根據(jù) total 算出總頁數(shù)。
- 獲取到所有的參數(shù),執(zhí)行請求邏輯。
- 當修改當前頁或者當前每一頁的最大數(shù)量的時候,直接調(diào)用 onChange 方法。
// c,代表 current page // p,代表 page size const onChange = (c: number, p: number) => { let toCurrent = c <= 0 ? 1 : c; const toPageSize = p <= 0 ? 1 : p; // 根據(jù) total 算出總頁數(shù) const tempTotalPage = Math.ceil(total / toPageSize); // 假如此時總頁面小于當前頁面,需要將當前頁面賦值為總頁數(shù) if (toCurrent > tempTotalPage) { toCurrent = Math.max(1, tempTotalPage); } const [oldPaginationParams = {}, ...restParams] = result.params || []; // 重新執(zhí)行請求 result.run( // 留意參數(shù)變化,主要是當前頁數(shù)和每頁的總數(shù)量發(fā)生變化 { ...oldPaginationParams, current: toCurrent, pageSize: toPageSize, }, ...restParams, ); }; const changeCurrent = (c: number) => { onChange(c, pageSize); }; const changePageSize = (p: number) => { onChange(current, p); };
最后返回請求的結(jié)果以及 pagination 字段,包含所有分頁信息。另外還有操作分頁的函數(shù)。
return { ...result, // 會額外返回 pagination 字段,包含所有分頁信息,及操作分頁的函數(shù)。 pagination: { current, pageSize, total, totalPage, onChange: useMemoizedFn(onChange), changeCurrent: useMemoizedFn(changeCurrent), changePageSize: useMemoizedFn(changePageSize), }, } as PaginationResult<TData, TParams>;
小結(jié):usePagination 默認用法與 useRequest 一致,但內(nèi)部封裝了分頁請求相關的邏輯。返回的結(jié)果多返回一個 pagination 參數(shù),包含所有分頁信息,及操作分頁的函數(shù)。
缺點就是對 API 請求參數(shù)有所限制,比如入?yún)⒔Y(jié)構(gòu)必須為 { current: number, pageSize: number }
,返回結(jié)果為 { total: number, list: Item[] }
。
useAntdTable
useAntdTable 基于 useRequest 實現(xiàn),封裝了常用的 Ant Design Form 與 Ant Design Table 聯(lián)動邏輯,并且同時支持 antd v3 和 v4。
首先調(diào)用 usePagination 處理分頁的邏輯。
const useAntdTable = <TData extends Data, TParams extends Params>( service: Service<TData, TParams>, options: AntdTableOptions<TData, TParams> = {}, ) => { const { // form 實例 form, // 默認表單選項 defaultType = 'simple', // 默認參數(shù),第一項為分頁數(shù)據(jù),第二項為表單數(shù)據(jù)。[pagination, formData] defaultParams, manual = false, // refreshDeps 變化,會重置 current 到第一頁,并重新發(fā)起請求。 refreshDeps = [], ready = true, ...rest } = options; // 對分頁的邏輯進行處理 // 分頁也是對 useRequest 的再封裝 const result = usePagination<TData, TParams>(service, { manual: true, ...rest, }); // ... }
然后處理列表頁篩選 Form 表單的邏輯,這里支持 Antd v3 和 Antd v4 版本。
// 判斷是否為 Antd 的第四版本 const isAntdV4 = !!form?.getInternalHooks;
獲取當前表單值,form.getFieldsValue
或者 form.getFieldInstance
:
// 獲取當前的 from 值 const getActivetFieldValues = () => { if (!form) { return {}; } // antd 4 if (isAntdV4) { return form.getFieldsValue(null, () => true); } // antd 3 const allFieldsValue = form.getFieldsValue(); const activeFieldsValue = {}; Object.keys(allFieldsValue).forEach((key: string) => { if (form.getFieldInstance ? form.getFieldInstance(key) : true) { activeFieldsValue[key] = allFieldsValue[key]; } }); return activeFieldsValue; };
校驗表單邏輯 form.validateFields
:
// 校驗邏輯 const validateFields = (): Promise<Record<string, any>> => { if (!form) { return Promise.resolve({}); } const activeFieldsValue = getActivetFieldValues(); const fields = Object.keys(activeFieldsValue); // antd 4 // validateFields 直接調(diào)用 if (isAntdV4) { return (form.validateFields as Antd4ValidateFields)(fields); } // antd 3 return new Promise((resolve, reject) => { form.validateFields(fields, (errors, values) => { if (errors) { reject(errors); } else { resolve(values); } }); }); };
重置表單 form.setFieldsValue
:
// 重置表單 const restoreForm = () => { if (!form) { return; } // antd v4 if (isAntdV4) { return form.setFieldsValue(allFormDataRef.current); } // antd v3 const activeFieldsValue = {}; Object.keys(allFormDataRef.current).forEach((key) => { if (form.getFieldInstance ? form.getFieldInstance(key) : true) { activeFieldsValue[key] = allFormDataRef.current[key]; } }); form.setFieldsValue(activeFieldsValue); };
修改表單類型,支持 'simple'
和 'advance'
。初始化的表單數(shù)據(jù)可以填寫 simple 和 advance 全量的表單數(shù)據(jù),開發(fā)者可以根據(jù)當前激活的類型來設置表單數(shù)據(jù)。修改 type 的時候會重置 form 表單數(shù)據(jù)。
const changeType = () => { // 獲取當前表單值 const activeFieldsValue = getActivetFieldValues(); // 修改表單值 allFormDataRef.current = { ...allFormDataRef.current, ...activeFieldsValue, }; // 設置表單類型 setType((t) => (t === 'simple' ? 'advance' : 'simple')); }; // 修改 type,則重置 form 表單數(shù)據(jù) useUpdateEffect(() => { if (!ready) { return; } restoreForm(); }, [type]);
_submit
方法:對 form 表單校驗后,根據(jù)當前 form 表單數(shù)據(jù)、分頁等篩選條件進行對表格數(shù)據(jù)搜索。
const _submit = (initPagination?: TParams[0]) => { setTimeout(() => { // 先進行校驗 validateFields() .then((values = {}) => { // 分頁的邏輯 const pagination = initPagination || { pageSize: options.defaultPageSize || 10, ...(params?.[0] || {}), current: 1, }; // 假如沒有 form,則直接根據(jù)分頁的邏輯進行請求 if (!form) { // @ts-ignore run(pagination); return; } // 獲取到當前所有 form 的 Data 參數(shù) // record all form data allFormDataRef.current = { ...allFormDataRef.current, ...values, }; // @ts-ignore run(pagination, values, { allFormData: allFormDataRef.current, type, }); }) .catch((err) => err); }); };
另外當表格觸發(fā) onChange 方法的時候,也會進行請求:
// Table 組件的 onChange 事件 const onTableChange = (pagination: any, filters: any, sorter: any) => { const [oldPaginationParams, ...restParams] = params || []; run( // @ts-ignore { ...oldPaginationParams, current: pagination.current, pageSize: pagination.pageSize, filters, sorter, }, ...restParams, ); };
初始化的時候,會根據(jù)當前是否有緩存的數(shù)據(jù),有則根據(jù)緩存的數(shù)據(jù)執(zhí)行請求邏輯。否則,通過 manual
和 ready
判斷是否需要進行重置表單后執(zhí)行請求邏輯。
// 初始化邏輯 // init useEffect(() => { // if has cache, use cached params. ignore manual and ready. // params.length > 0,則說明有緩存 if (params.length > 0) { // 使用緩存的數(shù)據(jù) allFormDataRef.current = cacheFormTableData?.allFormData || {}; // 重置表單后執(zhí)行請求 restoreForm(); // @ts-ignore run(...params); return; } // 非手動并且已經(jīng) ready,則執(zhí)行 _submit if (!manual && ready) { allFormDataRef.current = defaultParams?.[1] || {}; restoreForm(); _submit(defaultParams?.[0]); } }, []);
最后,將請求返回的數(shù)據(jù)通過 dataSource、 pagination、loading 透傳回給到 Table 組件,實現(xiàn) Table 的數(shù)據(jù)以及狀態(tài)的展示。以及將對 Form 表單的一些操作方法暴露給開發(fā)者。
return { ...result, // Table 組件需要的數(shù)據(jù),直接透傳給 Table 組件即可 tableProps: { dataSource: result.data?.list || defaultDataSourceRef.current, loading: result.loading, onChange: useMemoizedFn(onTableChange), pagination: { current: result.pagination.current, pageSize: result.pagination.pageSize, total: result.pagination.total, }, }, search: { // 提交表單 submit: useMemoizedFn(submit), // 當前表單類型, simple | advance type, // 切換表單類型 changeType: useMemoizedFn(changeType), // 重置當前表單 reset: useMemoizedFn(reset), }, } as AntdTableResult<TData, TParams>;
以上就是列表頁常見 hook 封裝示例的詳細內(nèi)容,更多關于列表頁 hook 封裝的資料請關注腳本之家其它相關文章!
相關文章
react如何修改循環(huán)數(shù)組對象的數(shù)據(jù)
這篇文章主要介紹了react如何修改循環(huán)數(shù)組對象的數(shù)據(jù)問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-12-12React中useTransition鉤子函數(shù)的使用詳解
React?18的推出標志著React并發(fā)特性的正式到來,其中useTransition鉤子函數(shù)是一個重要的新增功能,下面我們就來學習一下useTransition鉤子函數(shù)的具體使用吧2024-02-02