这个系列是将 ahooks 里面的所有 hook 源码都进行解读,通过解读 ahooks 的源码来熟悉自定义 hook 的写法,提高自己写自定义 hook 的能力,希望能够对大家有所帮助。
为了和代码原始注释区分,个人理解部分使用 ///开头,此处和 三斜线指令没有关系,只是为了做区分。
往期回顾
- ahooks 源码解读系列
- ahooks 源码解读系列 - 2
- ahooks 源码解读系列 - 3
- ahooks 源码解读系列 - 4
- ahooks 源码解读系列 - 5
- ahooks 源码解读系列 - 6
- ahooks 源码解读系列 - 7
- ahooks 源码解读系列 - 8
- ahooks 源码解读系列 - 9
- ahooks 源码解读系列 - 10
- ahooks 源码解读系列 - 11
- ahooks 源码解读系列 - 12
- ahooks 源码解读系列 - 13
- ahooks 源码解读系列 - 14
- ahooks 源码解读系列 - 15
来到最后一部分啦,Table 部分的 hooks ~
完结撒花,感谢大家一直来的点赞、关注 ~
话说我觉得我一开始取的名字不太对,应该叫 “逐行一字不拉之硬啃 ahooks 源码系列” 才对,🤦♀️ 。。。
Table
对常用列表场景进行了封装。
useAntdTable
梦开始的地方~ 看名字就知道是为 antd 量身定制的
import useRequest from '@ahooksjs/use-request';
import { useState, useCallback, useEffect, useRef } from 'react';
import {
CombineService,
PaginatedParams,
BasePaginatedOptions,
PaginatedOptionsWithFormat,
PaginatedFormatReturn,
PaginatedResult,
} from '@ahooksjs/use-request/lib/types';
import useUpdateEffect from '../useUpdateEffect';
import usePersistFn from '../usePersistFn';
export {
CombineService,
PaginatedParams,
BasePaginatedOptions,
PaginatedOptionsWithFormat,
PaginatedFormatReturn,
PaginatedResult,
};
export interface Store {
[name: string]: any;
}
type Antd3ValidateFields = (fieldNames: string[], callback: (errors, values) => void) => void;
type Antd4ValidateFields = (fieldNames?: string[]) => Promise<any>;
export interface UseAntdTableFormUtils {
getFieldInstance?: (name: string) => {}; // antd 3
setFieldsValue: (value: Store) => void;
getFieldsValue: (...args: any) => Store;
resetFields: (...args: any) => void;
validateFields: Antd3ValidateFields | Antd4ValidateFields;
[key: string]: any;
}
export interface Result<Item> extends PaginatedResult<Item> {
search: {
type: 'simple' | 'advance';
changeType: () => void;
submit: () => void;
reset: () => void;
};
}
export interface BaseOptions<U> extends Omit<BasePaginatedOptions<U>, 'paginated'> {
form?: UseAntdTableFormUtils;
defaultType?: 'simple' | 'advance';
}
export interface OptionsWithFormat<R, Item, U>
extends Omit<PaginatedOptionsWithFormat<R, Item, U>, 'paginated'> {
form?: UseAntdTableFormUtils;
defaultType?: 'simple' | 'advance';
}
function useAntdTable<R = any, Item = any, U extends Item = any>(
service: CombineService<R, PaginatedParams>,
options: OptionsWithFormat<R, Item, U>,
): Result<Item>;
function useAntdTable<R = any, Item = any, U extends Item = any>(
service: CombineService<PaginatedFormatReturn<Item>, PaginatedParams>,
options: BaseOptions<U>,
): Result<Item>;
function useAntdTable<R = any, Item = any, U extends Item = any>(
service: CombineService<any, any>,
options: BaseOptions<U> | OptionsWithFormat<R, Item, U>,
): any {
const {
form,
refreshDeps = [],
manual,
defaultType = 'simple',
defaultParams,
...restOptions
} = options;
/// 使用 paginated 和 manual 模式
const result = useRequest(service, {
...restOptions,
paginated: true as true,
manual: true,
});
const { params, run } = result;
/// 在下面的 _submit 方法中,会将全量的表单数据和上次的请求type 作为第三个参数传入 run 方法
const cacheFormTableData = params[2] || ({} as any);
// 优先从缓存中读
const [type, setType] = useState(cacheFormTableData.type || defaultType);
// 全量 form 数据,包括 simple 和 advance
const [allFormData, setAllFormData] = useState<Store>(
cacheFormTableData.allFormData || (defaultParams && defaultParams[1]) || {},
);
/// 之所以会有当前展示的 form 这种说法,是因为内置了一个切换表单的功能
/// 本 hook 支持渲染两种类型的 form ,一种 simple 一种 advance
/// 可以很方便的实现那种展开更多搜索条件的交互
// 获取当前展示的 form 字段值
const getActivetFieldValues = useCallback((): Store => {
if (!form) {
return {};
}
// antd 3
if (form.getFieldInstance) {
const tempAllFiledsValue = form.getFieldsValue();
const filterFiledsValue: Store = {};
Object.keys(tempAllFiledsValue).forEach((key: string) => {
if (form.getFieldInstance ? form.getFieldInstance(key) : true) {
filterFiledsValue[key] = tempAllFiledsValue[key];
}
});
return filterFiledsValue;
}
// antd 4
return form.getFieldsValue(null, () => true);
}, [form]);
const formRef = useRef(form);
formRef.current = form;
/* 初始化,或改变了 searchType, 恢复表单数据 */
useEffect(() => {
if (!formRef.current) {
return;
}
// antd 3
if (formRef.current.getFieldInstance) {
// antd 3 需要判断字段是否存在,否则会抛警告
const filterFiledsValue: Store = {};
Object.keys(allFormData).forEach((key: string) => {
if (formRef.current!.getFieldInstance ? formRef.current!.getFieldInstance(key) : true) {
filterFiledsValue[key] = allFormData[key];
}
});
formRef.current.setFieldsValue(filterFiledsValue);
} else {
// antd 4
formRef.current.setFieldsValue(allFormData);
}
}, [type]);
// 首次加载,手动提交。为了拿到 form 的 initial values
useEffect(() => {
// 如果有缓存,则使用缓存,重新请求
if (params.length > 0) {
run(...params);
return;
}
// 如果没有缓存,触发 submit
if (!manual) {
_submit(defaultParams);
}
}, []);
/// 在切换 type 之前,将当前表单的值和之前记录的值合并之后记录下来,达到支持两种表单的目的
const changeType = useCallback(() => {
const currentFormData = getActivetFieldValues();
setAllFormData({ ...allFormData, ...currentFormData });
const targetType = type === 'simple' ? 'advance' : 'simple';
setType(targetType);
}, [type, allFormData, getActivetFieldValues]);
const validateFields: () => Promise<any> = useCallback(() => {
const fieldValues = getActivetFieldValues();
if (!form) {
return Promise.resolve();
}
/// 根据当前表单的值,确定需要校验的字段名
const fields = Object.keys(fieldValues);
/// 使用特性检测,不满足则 validateFields 返回的不是 promise
if (!form.getInternalHooks) {
return new Promise((resolve, reject) => {
form.validateFields(fields, (errors, values) => {
if (errors) {
reject(errors);
} else {
resolve(values);
}
});
});
}
return (form.validateFields as Antd4ValidateFields)(fields);
}, [form]);
const _submit = useCallback(
(initParams?: any) => {
/// 放在 setTimeout 中,也就是等待下一次宏队列再运行,应该是防止 changeType 之后立刻提交导致提交的表单数据不对
setTimeout(() => {
validateFields()
.then(() => {
const activeFormData = getActivetFieldValues();
// 记录全量数据
const _allFormData = { ...allFormData, ...activeFormData };
setAllFormData(_allFormData);
/// 使用三个参数调用 run :分页相关参数、搜索表单数据、全量搜索表单数据以及表单类型
// has defaultParams
if (initParams) {
run(initParams[0], activeFormData, {
allFormData: _allFormData,
type,
});
return;
}
run(
{
pageSize: options.defaultPageSize || 10,
...((params[0] as PaginatedParams[0] | undefined) || {}), // 防止 manual 情况下,第一次触发 submit,此时没有 params[0]
current: 1,
},
activeFormData,
{
allFormData: _allFormData,
type,
},
);
})
.catch((err) => err);
});
},
[getActivetFieldValues, run, params, allFormData, type],
);
const reset = useCallback(() => {
if (form) {
form.resetFields();
}
_submit();
}, [form, _submit]);
const resetPersistFn = usePersistFn(reset);
// refreshDeps 变化,reset。
useUpdateEffect(() => {
if (!manual) {
resetPersistFn();
}
}, [...refreshDeps]);
const submit = usePersistFn((e) => {
if (e && e.preventDefault) {
e.preventDefault();
}
_submit();
});
return {
...result,
search: {
submit,
type,
changeType,
reset,
},
};
}
export default useAntdTable;
useFusionTable
为 fusion form 和 fusion table 量身定做的
import {
CombineService,
PaginatedParams,
BasePaginatedOptions,
PaginatedOptionsWithFormat,
PaginatedFormatReturn,
PaginatedResult,
} from '@ahooksjs/use-request/lib/types';
import useAntdTable from '../useAntdTable';
import { fieldAdapter, resultAdapter } from './fusionAdapter';
export {
CombineService,
PaginatedParams,
BasePaginatedOptions,
PaginatedOptionsWithFormat,
PaginatedFormatReturn,
PaginatedResult,
};
export interface Store {
[name: string]: any;
}
export interface Field {
getFieldInstance?: (name: string) => {}; // antd 3
setValues: (value: Store) => void;
getValues: (...args: any) => Store;
reset: (...args: any) => void;
validate: (callback: (errors, values) => void) => void;
[key: string]: any;
}
export interface Result<Item> extends Omit<PaginatedResult<Item>, 'tableProps'> {
paginationProps: {
onChange: (current: number) => void;
onPageSizeChange: (size: number) => void;
current: number;
pageSize: number;
total: number;
};
tableProps: {
dataSource: Item[];
loading: boolean;
onSort: (dataIndex: String, order: String) => void;
onFilter: (filterParams: Object) => void;
};
search: {
type: 'simple' | 'advance';
changeType: () => void;
submit: () => void;
reset: () => void;
};
}
export interface BaseOptions<U> extends Omit<BasePaginatedOptions<U>, 'paginated'> {
field?: Field;
defaultType?: 'simple' | 'advance';
}
export interface OptionsWithFormat<R, Item, U>
extends Omit<PaginatedOptionsWithFormat<R, Item, U>, 'paginated'> {
field?: Field;
defaultType?: 'simple' | 'advance';
}
function useFusionTable<R = any, Item = any, U extends Item = any>(
service: CombineService<R, PaginatedParams>,
options: OptionsWithFormat<R, Item, U>,
): Result<Item>;
function useFusionTable<R = any, Item = any, U extends Item = any>(
service: CombineService<PaginatedFormatReturn<Item>, PaginatedParams>,
options: BaseOptions<U>,
): Result<Item>;
/// 主要是使用 fieldAdapter 将 field 转化成 form
/// 使用 resultAdapter 将结果转化成 fusion 需要的形式
/// 可以理解为一个已经处理了输入输出的 useAntdTable 的语法糖
function useFusionTable<R = any, Item = any, U extends Item = any>(
service: CombineService<any, any>,
options: BaseOptions<U> | OptionsWithFormat<R, Item, U>,
): any {
const ret = useAntdTable(service, {
...options,
form: options.field ? fieldAdapter(options.field) : undefined,
});
return resultAdapter(ret);
}
export default useFusionTable;
下面是核心转化方法
import { Field } from './index';
export interface Store {
[name: string]: any;
}
interface UseAntdTableFormUtils {
getFieldInstance?: (name: string) => {}; // antd 3
setFieldsValue: (value: Store) => void;
getFieldsValue: (...args: any) => Store;
resetFields: (...args: any) => void;
validateFields: () => Promise<any>;
[key: string]: any;
}
/// 将 fusion 的方法转化为 antd 的方法
export const fieldAdapter = (field: Field) =>
({
getFieldInstance: (name: string) => field.getNames().includes(name),
setFieldsValue: field.setValues,
getFieldsValue: field.getValues,
resetFields: field.reset,
validateFields: (fields, callback) => {
field.validate(callback);
},
} as UseAntdTableFormUtils);
/// 将 useAntdTable 的返回值转化为 fusion 需要的格式
export const resultAdapter = (result: any) => {
const tableProps = {
dataSource: result.tableProps.dataSource,
loading: result.tableProps.loading,
onSort: (dataIndex: String, order: String) => {
result.tableProps.onChange(
{ current: result.pagination.current, pageSize: result.pagination.pageSize },
result.filters,
{
field: dataIndex,
order,
},
);
},
onFilter: (filterParams: Object) => {
result.tableProps.onChange(
{ current: result.pagination.current, pageSize: result.pagination.pageSize },
filterParams,
result.sorter,
);
},
};
const paginationProps = {
onChange: result.pagination.changeCurrent,
onPageSizeChange: result.pagination.changePageSize,
current: result.pagination.current,
pageSize: result.pagination.pageSize,
total: result.pagination.total,
};
return {
...result,
tableProps,
paginationProps,
};
};
以上内容由于本人水平问题难免有误,欢迎大家进行讨论反馈。