React封装强业务hook的一个例子

更新日期: 2020-01-27阅读: 1.8k标签: 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 };
}


 下面是业务代码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]);
}


链接: https://www.fly63.com/article/detial/8435

分享 10 个可以使用 Vue.js 制作的有用的自定义钩hook

Vue.js 是我使用的第一个 JavaScript 框架。 我可以说 Vue.js 是我进入 JavaScript 世界的第一扇门之一。 目前,Vue.js 仍然是一个很棒的框架。 我认为有了组合 API,Vue.js 只会增长得更多

pytest插件探索——hook开发

conftest.py可以作为最简单的本地plugin调用一些hook函数,以此来做些强化功能。pytest整个框架通过调用如下定义良好的hooks来实现配置,收集,执行和报告这些过程:

React中useState Hook 示例

到 React 16.8 目前为止,如果编写函数组件,然后遇到需要添加状态的情况,咱们就必须将组件转换为类组件。编写 class Thing extends React.Component,将函数体复制到render()方法中,修复缩进,最后添加需要的状态。

useContext Hook 是如何工作的?

所有这些新的React Hook之间都有一个宗旨:就是为了使函数组件像类组件一样强大。useContext hook 与其它几个有点不一样,但它在特定场景下还是很有用的。React 的 Context API 是一种在应用程序中深入传递数据的方法

结合React的Effect Hook分析组件副作用的清除

我们在DidMount的时候通过ID订阅了好友的在线状态,并且为了防止内存泄漏,我们需要在WillUnmount清除订阅,但是当组件已经显示在屏幕上时,friend prop 发生变化时会发生什么?

结合高阶函数聊聊useMemo和useCallback

useCallback和useMemo是其中的两个 hooks,本文旨在通过解决一个需求,结合高阶函数,深入理解useCallback和useMemo的用法和使用场景。 之所以会把这两个 hooks 放到一起说,是因为他们的主要作用都是性能优化

关于为什么使用React新特性Hook的一些实践与浅见

Hook是对函数式组件的一次增强,使得函数式组件可以做到class组件的state和生命周期。Hook的语法更加简练易懂,消除了class的生命周期方法导致的重复逻辑代码,解决了高阶组件难以理解和使用困难的问题。

React官方团队出手,补齐原生Hook短板

然而实际上,由于回调函数被useCallback缓存,形成闭包,所以点击的效果始终是sendMessage()。这就是「闭包陷阱」。以上代码的一种解决方式是「为useCallback增加依赖项」

实现一个自定义 React Hook:UseLocalStorageState

最近做需求,需要将数据保存到 localStorage 里,在组件初始化的时候获取,然后修改该值的时候,要保存到本地的 localStorage 中。很显然,这些逻辑完全可以封装为一个 React Hook

为什么Hook没有ErrorBoundary?

在很多全面使用Hooks开发的团队,唯一使用ClassComponent的场景就是使用ClassComponent创建ErrorBoundary。可以说,如果Hooks存在如下两个生命周期函数的替代品,就能全面抛弃ClassComponent了:

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!