React封装强业务hook的一个例子
最近因为使用列表展示的需求有点多,就想着把列表分页筛选的逻辑抽象一下。看了umi的一个useTable的hook,也不能满足业务需要,于是就自己写了一个,支持本地分页筛选和接口分页筛选。
思路就是,筛选的字段都使用form表单控制,然后在hook里面将form和table联合起来。
下面贴出源码
useFormTable.tsx
import { TableProps, PaginationProps } from '@slardar/antd';
import react, { useEffect } from 'react';
import {
PaginationConfig,
SorterResult,
TableCurrentDataSource
} from 'antd/lib/table';
import { useDeepCompareEffect } from '@byted-woody/slardar';
export type WrappedFormUtils = any;
export interface TableControlState<T> extends TableProps<DataRow> {
pagination: PaginationProps;
sorter?: SorterResult<DataRow>;
loading?: boolean;
}
// 搜索参数描述
export interface SearchDescribe {
// 字段名
fieldName: string;
iniValue?: any;
// 输入值解析,比如日期输入解析
decodeFn?: any;
// 自定义搜索函数
searchFn?: (record: DataRow, desc: SearchDescribe) => boolean;
// 解析后的值
searchValue?: any;
// 调用接口或者只在本地过滤
searchMod?: 'api' | 'local';
// 搜索的字段,默认只搜索当前字段
searchFields?: string[];
}
export interface DataReceive {
pageSize?: number;
pageIndex?: number;
pageTotal?: number;
pageData: any[];
}
export type DataRow = { [key: string]: any };
export type DataApiGet = (
apiParams,
pagination?: { pageIndex?: number; pageSize?: number }
) => Promise<DataReceive>;
export interface FormTableReq {
form: WrappedFormUtils;
getDataApi: DataApiGet;
getDataParam?: { [key: string]: any };
// 表单的字段解析
includeFormFields?: (SearchDescribe | string)[];
// 本地分页
localPagination?: boolean;
// 本地搜索
localSearch?: boolean;
// 本地分页+搜索
local?: boolean;
afterFetchData?: (v: any) => void;
validateParam?: (param: any) => boolean;
}
const defaultTableState: TableControlState<DataRow> = {
pagination: { current: 1, total: 0, pageSize: 10 },
dataSource: [],
loading: true
};
export type FormTableRet = [
TableControlState<DataRow>,
{ fetchData: () => void }
];
export function useFormTable(options: FormTableReq): FormTableRet {
if (options.local) {
options?.includeFormFields?.forEach(d => (d.searchMod = 'local'));
return useFormTableLocal(options);
} else {
return useFormTableDB(options);
}
}
// 本地分页筛选版本
export function useFormTableLocal(options: FormTableReq): FormTableRet {
let { form, getDataApi, includeFormFields } = options;
let currentFormValue = form.getFieldsValue();
// 缓存数据
let cacheDataListRef = React.useRef<DataRow[]>([]);
let [tableState, setTableState] = React.useState<TableControlState<DataRow>>(
defaultTableState
);
let searchApiParam = {};
let searchLocalParam: SearchDescribe[] = [];
if (Array.isArray(includeFormFields)) {
includeFormFields?.forEach(describe => {
if (typeof describe === 'string') {
let value = currentFormValue[describe];
searchApiParam[describe] = value;
} else {
let value = currentFormValue[describe.fieldName];
if (describe.decodeFn) {
value = describe.decodeFn(value);
}
if (describe.searchMod === 'api') {
searchApiParam[describe.fieldName] = value;
} else {
searchLocalParam.push(
Object.assign({ searchValue: value }, describe)
);
}
}
});
} else {
searchApiParam = currentFormValue;
}
function getTableApiData() {
getDataApi(searchApiParam).then(data => {
cacheDataListRef.current = data.pageData;
setTableState(prevState => {
return Object.assign({}, prevState, { dataSource: [] });
});
});
}
useEffect(getTableApiData, []);
let { data, total } = calculatePageData(
tableState,
cacheDataListRef.current,
searchLocalParam
);
function onSorterChange(
_pagination: PaginationConfig,
_filters: Record<keyof DataRow, string[]>,
_sorter: SorterResult<DataRow>,
_extra: TableCurrentDataSource<DataRow>
) {
setTableState(prevState => {
return Object.assign({}, prevState, { sorter: _sorter });
});
}
let newPage: PaginationProps = {
total: total,
onChange: (page, pageSize) => {
setTableState(prevState => {
prevState.pagination.pageSize = pageSize;
prevState.pagination.current = page;
return Object.assign({}, prevState);
});
}
};
let finalPagination: PaginationProps = Object.assign(
{},
tableState.pagination,
newPage
);
return [
{ pagination: finalPagination, dataSource: data, onChange: onSorterChange },
{ fetchData: getTableApiData }
];
}
// 接口分页筛选版本 待完善
export function useFormTableDB(options: FormTableReq): FormTableRet {
let { form, getDataApi, includeFormFields } = options;
let currentFormValue = form.getFieldsValue();
let [state, setState] = React.useState<TableControlState<DataRow>>(
defaultTableState
);
let searchApiParam: { [key: string]: any } = {};
let onceRef = React.useRef(false);
// 计算接口参数
if (Array.isArray(includeFormFields)) {
includeFormFields?.forEach(describe => {
if (typeof describe === 'string') {
let value = currentFormValue[describe];
searchApiParam[describe] = value;
} else {
let value = currentFormValue[describe.fieldName];
if (!onceRef.current && describe.iniValue) {
value = describe.iniValue;
}
if (describe.decodeFn) {
value = describe.decodeFn(value);
Object.assign(searchApiParam, value);
} else {
searchApiParam[describe.fieldName] = value;
}
}
});
} else {
searchApiParam = currentFormValue;
}
Object.assign(searchApiParam, options.getDataParam);
const pageParam = {
pageIndex: state.pagination.current,
pageSize: state.pagination.pageSize
};
function getTableApiData() {
if (options.validateParam && !options.validateParam(searchApiParam)) {
return;
}
setState(prevState => {
return Object.assign({}, prevState, {
loading: true
} as TableControlState<any>);
});
getDataApi(searchApiParam, pageParam).then(data => {
const { pageData, pageTotal } = data;
onceRef.current = true;
setState(prevState => {
return Object.assign({}, prevState, {
dataSource: pageData,
pagination: {
current: pageParam.pageIndex,
total: pageTotal || 0,
pageSize: pageParam.pageSize
},
loading: false
} as TableControlState<any>);
});
// 将表单数据同步到query
if (options.afterFetchData) {
options.afterFetchData(currentFormValue);
}
});
}
useDeepCompareEffect(getTableApiData, [searchApiParam, pageParam]);
function onSorterChange(
_pagination: PaginationConfig,
_filters: Record<keyof DataRow, string[]>,
_sorter: SorterResult<DataRow>,
_extra: TableCurrentDataSource<DataRow>
) {
setState(prevState => {
return Object.assign({}, prevState, { sorter: _sorter });
});
}
let finalPagination: PaginationProps = Object.assign(
{
total: state.pagination.total,
onChange: (page, pageSize) => {
setState(prevState => {
prevState.pagination.pageSize = pageSize;
prevState.pagination.current = page;
return Object.assign({}, prevState);
});
}
},
state.pagination
);
let dataSource = state.dataSource;