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;
if (options.localPagination) {
let { data, total } = calculatePageData(state, state.dataSource as any, []);
finalPagination.total = total;
dataSource = data;
}

return [
{
pagination: finalPagination,
dataSource: dataSource,
onChange: onSorterChange,
loading: state.loading
},
{ fetchData: getTableApiData },
state
] as any;
}

// 排序,筛选,计算分页数据
function calculatePageData(
state: TableControlState<DataRow>,
dataList: DataRow[],
param: SearchDescribe[]
) {
let { pagination, sorter } = state;
let { current = 1, pageSize = 10 } = pagination;
let copyDataList = Array.from(dataList);
// 排序
if (sorter?.column) {
let order = sorter.order;
let sortField = sorter.columnKey;
copyDataList = copyDataList.sort((a, b) => {
if (order === 'ascend') {
return a[sortField] - b[sortField];
} else {
return b[sortField] - a[sortField];
}
});
}
// 筛选
if (Array.isArray(param) && param.length > 0) {
copyDataList = copyDataList.filter(function filter(v) {
return param.every(desc => {
let { fieldName, searchValue, searchFields, searchFn } = desc;
let fieldValue = v[fieldName];
let searchString = searchValue;
if (!searchString) {
return true;
}
if (searchFn) {
return searchFn(v, desc);
}
if (
typeof fieldValue !== 'string' ||
typeof searchString !== 'string'
) {
return true;
}
if (searchFields?.length) {
return searchFields?.some(fieldName => {
let value = v[fieldName];
if (typeof value === 'string') {
value.includes(searchString);
}
return false;
});
} else {
return fieldValue.includes(searchString);
}
});
});
}
// 分页
let displayData = copyDataList.slice(
(current - 1) * pageSize,
current * pageSize
);
// 默认空数据的展示
displayData.forEach(d => {
Object.entries(d).forEach(([k, v]) => {
if (v !== 0 && !v) {
d[k] = '---';
}
});
return d;
});
return { data: displayData, total: copyDataList.length };
}

vi设计http://www.maiqicn.com 办公资源网站大全https://www.wode007.com

 下面是业务代码demo

Demo.tsx  

import React, { FC } from 'react';
import { Form, FormComponentProps, Input, Table } from '@slardar/antd';
import { useFormTable } from '@slardar/common-modules';

const FormItem = Form.Item;

interface IProps extends FormComponentProps {}
const DemoComponent: FC<IProps> = function(props) {
const form = props.form;
let [tableState] = useFormTable({
form: props.form,
getDataApi: () => Promise.resolve([] as any),
includeFormFields: ['name', 'search']
});

return (
<div className={'alarm-page-content'}>
<Form layout={'inline'}>
<FormItem>
{form.getFieldDecorator('name')(
<Input.Search
style={{ marginLeft: 16, width: 150 }}
placeholder="名称"
/>
)}
</FormItem>
<FormItem>
{form.getFieldDecorator('search')(
<Input.Search
style={{ marginLeft: 16, width: 150 }}
placeholder="评论"
/>
)}
</FormItem>
</Form>
<Table {...tableState} columns={[]} />
</div>
);
};

export const Demo = Form.create()(DemoComponent);

useDeepCompareEffect.ts  

import { useRef, useEffect } from 'react';
import _ from 'lodash';
export function useDeepCompareEffect<T>(fn, deps: T) {
// 使用一个数字信号控制是否渲染,简化 react 的计算,也便于调试
let renderRef = useRef<number | any>(0);
let depsRef = useRef<T>(deps);
if (!_.isEqual(deps, depsRef.current)) {
renderRef.current++;
}
depsRef.current = deps;
return useEffect(fn, [renderRef.current]);
}
posted @ 2020-10-03 17:48  笑人  阅读(379)  评论(0编辑  收藏  举报