type(Table): TypeScript improvement (#50351)

* type: Table TypeScript improvement

* type: Table TypeScript improvement

* type: Table TypeScript improvement

* fix: fix demo

* fix: fix demo

* fix: fix type

* fix: fix type

* fix: fix type

* fix: fix type
This commit is contained in:
lijianan 2024-08-11 10:58:13 +08:00 committed by GitHub
parent f05c3809d5
commit 857ee24e38
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
23 changed files with 233 additions and 197 deletions

View File

@ -1,14 +1,14 @@
import type { AnyObject } from '../_util/type';
import type { ColumnType } from './interface';
export interface ColumnProps<RecordType> extends ColumnType<RecordType> {
export interface ColumnProps<RecordType extends AnyObject = AnyObject>
extends ColumnType<RecordType> {
children?: null;
}
/* istanbul ignore next */
/** This is a syntactic sugar for `columns` prop. So HOC will not work on this. */
// eslint-disable-next-line no-unused-vars
function Column<RecordType>(_: ColumnProps<RecordType>) {
return null;
}
const Column = <RecordType extends AnyObject>(_: ColumnProps<RecordType>) => null;
export default Column;

View File

@ -1,9 +1,11 @@
import type * as React from 'react';
import type { AnyObject } from '../_util/type';
import type { ColumnProps } from './Column';
import type { ColumnType } from './interface';
export interface ColumnGroupProps<RecordType> extends Omit<ColumnType<RecordType>, 'children'> {
export interface ColumnGroupProps<RecordType extends AnyObject = AnyObject>
extends Omit<ColumnType<RecordType>, 'children'> {
children:
| React.ReactElement<ColumnProps<RecordType>>
| React.ReactElement<ColumnProps<RecordType>>[];
@ -12,8 +14,6 @@ export interface ColumnGroupProps<RecordType> extends Omit<ColumnType<RecordType
/* istanbul ignore next */
/** This is a syntactic sugar for `columns` prop. So HOC will not work on this. */
// eslint-disable-next-line no-unused-vars
function ColumnGroup<RecordType>(_: ColumnGroupProps<RecordType>) {
return null;
}
const ColumnGroup = <RecordType extends AnyObject>(_: ColumnGroupProps<RecordType>) => null;
export default ColumnGroup;

View File

@ -1,26 +1,21 @@
import * as React from 'react';
import classNames from 'classnames';
import type { AnyObject } from '../_util/type';
import type { TableLocale } from './interface';
interface DefaultExpandIconProps<RecordType> {
interface DefaultExpandIconProps<RecordType extends AnyObject = AnyObject> {
prefixCls: string;
onExpand: (record: RecordType, e: React.MouseEvent<HTMLElement>) => void;
record: RecordType;
expanded: boolean;
expandable: boolean;
onExpand: (record: RecordType, e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void;
}
function renderExpandIcon(locale: TableLocale) {
return function expandIcon<RecordType>({
prefixCls,
onExpand,
record,
expanded,
expandable,
}: DefaultExpandIconProps<RecordType>) {
return <RecordType extends AnyObject = AnyObject>(props: DefaultExpandIconProps<RecordType>) => {
const { prefixCls, onExpand, record, expanded, expandable } = props;
const iconPrefix = `${prefixCls}-row-expand-icon`;
return (
<button
type="button"

View File

@ -57,9 +57,9 @@ import useStyle from './style';
export type { ColumnsType, TablePaginationConfig };
const EMPTY_LIST: any[] = [];
const EMPTY_LIST: AnyObject[] = [];
interface ChangeEventInfo<RecordType> {
interface ChangeEventInfo<RecordType extends AnyObject = AnyObject> {
pagination: {
current?: number;
pageSize?: number;
@ -74,12 +74,7 @@ interface ChangeEventInfo<RecordType> {
resetPagination: (current?: number, pageSize?: number) => void;
}
/** Same as `TableProps` but we need record parent render times */
export interface InternalTableProps<RecordType> extends TableProps<RecordType> {
_renderTimes: number;
}
export interface TableProps<RecordType = any>
export interface TableProps<RecordType = AnyObject>
extends Omit<
RcTableProps<RecordType>,
| 'transformColumns'
@ -117,6 +112,12 @@ export interface TableProps<RecordType = any>
virtual?: boolean;
}
/** Same as `TableProps` but we need record parent render times */
export interface InternalTableProps<RecordType extends AnyObject = AnyObject>
extends TableProps<RecordType> {
_renderTimes: number;
}
const InternalTable = <RecordType extends AnyObject = AnyObject>(
props: InternalTableProps<RecordType>,
ref: React.MutableRefObject<HTMLDivElement>,
@ -176,9 +177,7 @@ const InternalTable = <RecordType extends AnyObject = AnyObject>(
const mergedColumns = React.useMemo(() => {
const matched = new Set(Object.keys(screens).filter((m) => screens[m as Breakpoint]));
return baseColumns.filter(
(c) => !c.responsive || c.responsive.some((r: Breakpoint) => matched.has(r)),
);
return baseColumns.filter((c) => !c.responsive || c.responsive.some((r) => matched.has(r)));
}, [baseColumns, screens]);
const tableProps: TableProps<RecordType> = omit(props, ['className', 'style', 'columns']);

View File

@ -1,13 +1,15 @@
import { genVirtualTable } from 'rc-table';
import type { AnyObject } from '../../_util/type';
import type { InternalTableProps } from '../InternalTable';
/**
* Same as `rc-table` but we modify trigger children update logic instead.
*/
export default genVirtualTable((prev, next) => {
const { _renderTimes: prevRenderTimes } = prev as InternalTableProps<any>;
const { _renderTimes: nextRenderTimes } = next as InternalTableProps<any>;
const RcVirtualTable = genVirtualTable((prev, next) => {
const { _renderTimes: prevRenderTimes } = prev as Readonly<InternalTableProps<AnyObject>>;
const { _renderTimes: nextRenderTimes } = next as Readonly<InternalTableProps<AnyObject>>;
return prevRenderTimes !== nextRenderTimes;
});
export default RcVirtualTable;

View File

@ -1,13 +1,15 @@
import { genTable } from 'rc-table';
import type { AnyObject } from '../../_util/type';
import type { InternalTableProps } from '../InternalTable';
/**
* Same as `rc-table` but we modify trigger children update logic instead.
*/
export default genTable((prev, next) => {
const { _renderTimes: prevRenderTimes } = prev as InternalTableProps<any>;
const { _renderTimes: nextRenderTimes } = next as InternalTableProps<any>;
const RcTable = genTable((prev, next) => {
const { _renderTimes: prevRenderTimes } = prev as Readonly<InternalTableProps<AnyObject>>;
const { _renderTimes: nextRenderTimes } = next as Readonly<InternalTableProps<AnyObject>>;
return prevRenderTimes !== nextRenderTimes;
});
export default RcTable;

View File

@ -4,7 +4,7 @@ import { Table } from 'antd';
import type { SorterResult } from 'antd/es/table/interface';
import qs from 'qs';
type ColumnsType<T> = TableProps<T>['columns'];
type ColumnsType<T extends object = object> = TableProps<T>['columns'];
type TablePaginationConfig = Exclude<GetProp<TableProps, 'pagination'>, boolean>;
interface DataType {
@ -94,7 +94,7 @@ const App: React.FC = () => {
JSON.stringify(tableParams.filters),
]);
const handleTableChange: TableProps['onChange'] = (pagination, filters, sorter) => {
const handleTableChange: TableProps<DataType>['onChange'] = (pagination, filters, sorter) => {
setTableParams({
pagination,
filters,

View File

@ -143,7 +143,7 @@ const App: React.FC = () => {
},
];
const mergedColumns: TableProps['columns'] = columns.map((col) => {
const mergedColumns: TableProps<Item>['columns'] = columns.map((col) => {
if (!col.editable) {
return col;
}

View File

@ -2,7 +2,7 @@ import React, { useState } from 'react';
import { Button, Flex, Table } from 'antd';
import type { TableColumnsType, TableProps } from 'antd';
type TableRowSelection<T> = TableProps<T>['rowSelection'];
type TableRowSelection<T extends object = object> = TableProps<T>['rowSelection'];
interface DataType {
key: React.Key;

View File

@ -2,7 +2,7 @@ import React from 'react';
import { Table } from 'antd';
import type { TableColumnsType, TableProps } from 'antd';
type TableRowSelection<T> = TableProps<T>['rowSelection'];
type TableRowSelection<T extends object = object> = TableProps<T>['rowSelection'];
interface DataType {
key: React.Key;

View File

@ -2,7 +2,7 @@ import React, { useState } from 'react';
import { Table } from 'antd';
import type { TableColumnsType, TableProps } from 'antd';
type TableRowSelection<T> = TableProps<T>['rowSelection'];
type TableRowSelection<T extends object = object> = TableProps<T>['rowSelection'];
interface DataType {
key: React.Key;

View File

@ -2,7 +2,7 @@ import React, { useState } from 'react';
import { InputNumber, Table } from 'antd';
import type { TableColumnsType, TableProps } from 'antd';
type TableRowSelection<T> = TableProps<T>['rowSelection'];
type TableRowSelection<T extends object = object> = TableProps<T>['rowSelection'];
const RenderTimes = () => {
const timesRef = React.useRef(0);

View File

@ -2,7 +2,7 @@ import React, { useState } from 'react';
import { Space, Switch, Table } from 'antd';
import type { TableColumnsType, TableProps } from 'antd';
type TableRowSelection<T> = TableProps<T>['rowSelection'];
type TableRowSelection<T extends object = object> = TableProps<T>['rowSelection'];
interface DataType {
key: React.ReactNode;

View File

@ -1,5 +1,6 @@
import * as React from 'react';
import FilterFilled from '@ant-design/icons/FilterFilled';
import type { AnyObject } from 'antd/es/_util/type';
import classNames from 'classnames';
import type { FieldDataNode } from 'rc-tree';
import isEqual from 'rc-util/lib/isEqual';
@ -119,7 +120,7 @@ function renderFilterItems({
export type TreeColumnFilterItem = ColumnFilterItem & FilterTreeDataNode;
export interface FilterDropdownProps<RecordType> {
export interface FilterDropdownProps<RecordType extends AnyObject = AnyObject> {
tablePrefixCls: string;
prefixCls: string;
dropdownPrefixCls: string;
@ -142,7 +143,9 @@ function wrapStringListType(keys?: FilterKey) {
return (keys as string[]) || [];
}
function FilterDropdown<RecordType>(props: FilterDropdownProps<RecordType>) {
const FilterDropdown = <RecordType extends AnyObject = AnyObject>(
props: FilterDropdownProps<RecordType>,
) => {
const {
tablePrefixCls,
prefixCls,
@ -553,6 +556,6 @@ function FilterDropdown<RecordType>(props: FilterDropdownProps<RecordType>) {
</Dropdown>
</div>
);
}
};
export default FilterDropdown;

View File

@ -1,10 +1,11 @@
import * as React from 'react';
import SearchOutlined from '@ant-design/icons/SearchOutlined';
import type { AnyObject } from '../../../_util/type';
import Input from '../../../input';
import type { FilterSearchType, TableLocale } from '../../interface';
interface FilterSearchProps<RecordType = any> {
interface FilterSearchProps<RecordType extends AnyObject = AnyObject> {
value: string;
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
filterSearch: FilterSearchType<RecordType>;
@ -12,13 +13,10 @@ interface FilterSearchProps<RecordType = any> {
locale: TableLocale;
}
function FilterSearch<RecordType>({
value,
onChange,
filterSearch,
tablePrefixCls,
locale,
}: FilterSearchProps<RecordType>) {
const FilterSearch = <RecordType extends AnyObject = AnyObject>(
props: FilterSearchProps<RecordType>,
) => {
const { value, filterSearch, tablePrefixCls, locale, onChange } = props;
if (!filterSearch) {
return null;
}
@ -35,6 +33,6 @@ function FilterSearch<RecordType>({
/>
</div>
);
}
};
export default FilterSearch;

View File

@ -2,7 +2,6 @@ import * as React from 'react';
import KeyCode from 'rc-util/lib/KeyCode';
export interface FilterDropdownMenuWrapperProps {
children?: React.ReactNode;
className?: string;
}
@ -13,8 +12,10 @@ const onKeyDown: React.KeyboardEventHandler<HTMLDivElement> = (event) => {
}
};
const FilterDropdownMenuWrapper = React.forwardRef<HTMLDivElement, FilterDropdownMenuWrapperProps>(
(props, ref) => (
const FilterDropdownMenuWrapper = React.forwardRef<
HTMLDivElement,
React.PropsWithChildren<FilterDropdownMenuWrapperProps>
>((props, ref) => (
<div
className={props.className}
onClick={(e) => e.stopPropagation()}
@ -23,8 +24,7 @@ const FilterDropdownMenuWrapper = React.forwardRef<HTMLDivElement, FilterDropdow
>
{props.children}
</div>
),
);
));
if (process.env.NODE_ENV !== 'production') {
FilterDropdownMenuWrapper.displayName = 'FilterDropdownMenuWrapper';

View File

@ -1,5 +1,6 @@
import * as React from 'react';
import type { AnyObject } from '../../../_util/type';
import { devUseWarning } from '../../../_util/warning';
import type {
ColumnsType,
@ -16,18 +17,18 @@ import type {
import { getColumnKey, getColumnPos, renderColumnTitle } from '../../util';
import FilterDropdown, { flattenKeys } from './FilterDropdown';
export interface FilterState<RecordType> {
export interface FilterState<RecordType extends AnyObject = AnyObject> {
column: ColumnType<RecordType>;
key: Key;
filteredKeys?: FilterKey;
forceFiltered?: boolean;
}
function collectFilterStates<RecordType>(
const collectFilterStates = <RecordType extends AnyObject = AnyObject>(
columns: ColumnsType<RecordType>,
init: boolean,
pos?: string,
): FilterState<RecordType>[] {
): FilterState<RecordType>[] => {
let filterStates: FilterState<RecordType>[] = [];
(columns || []).forEach((column, index) => {
@ -65,9 +66,9 @@ function collectFilterStates<RecordType>(
});
return filterStates;
}
};
function injectFilter<RecordType>(
function injectFilter<RecordType extends AnyObject = AnyObject>(
prefixCls: string,
dropdownPrefixCls: string,
columns: ColumnsType<RecordType>,
@ -112,7 +113,7 @@ function injectFilter<RecordType>(
getPopupContainer={getPopupContainer}
rootClassName={rootClassName}
>
{renderColumnTitle(column.title, renderProps)}
{renderColumnTitle<RecordType>(column.title, renderProps)}
</FilterDropdown>
),
};
@ -139,7 +140,9 @@ function injectFilter<RecordType>(
});
}
function generateFilterInfo<RecordType>(filterStates: FilterState<RecordType>[]) {
const generateFilterInfo = <RecordType extends AnyObject = AnyObject>(
filterStates: FilterState<RecordType>[],
) => {
const currentFilters: Record<string, FilterValue | null> = {};
filterStates.forEach(({ key, filteredKeys, column }) => {
@ -158,14 +161,14 @@ function generateFilterInfo<RecordType>(filterStates: FilterState<RecordType>[])
});
return currentFilters;
}
};
export function getFilterData<RecordType>(
export const getFilterData = <RecordType extends AnyObject = AnyObject>(
data: RecordType[],
filterStates: FilterState<RecordType>[],
childrenColumnName: string,
) {
return filterStates.reduce((currentData, filterState) => {
) => {
const filterDatas = filterStates.reduce<RecordType[]>((currentData, filterState) => {
const {
column: { onFilter, filters },
filteredKeys,
@ -197,9 +200,10 @@ export function getFilterData<RecordType>(
}
return currentData;
}, data);
}
return filterDatas;
};
export interface FilterConfig<RecordType> {
export interface FilterConfig<RecordType extends AnyObject = AnyObject> {
prefixCls: string;
dropdownPrefixCls: string;
mergedColumns: ColumnsType<RecordType>;
@ -212,17 +216,24 @@ export interface FilterConfig<RecordType> {
rootClassName?: string;
}
const getMergedColumns = <RecordType extends unknown>(
const getMergedColumns = <RecordType extends AnyObject = AnyObject>(
rawMergedColumns: ColumnsType<RecordType>,
): ColumnsType<RecordType> =>
rawMergedColumns.flatMap((column) => {
if ('children' in column) {
return [column, ...getMergedColumns(column.children || [])];
return [column, ...getMergedColumns<RecordType>(column.children || [])];
}
return [column];
});
function useFilter<RecordType>({
const useFilter = <RecordType extends AnyObject = AnyObject>(
props: FilterConfig<RecordType>,
): [
TransformColumns<RecordType>,
FilterState<RecordType>[],
Record<string, FilterValue | null>,
] => {
const {
prefixCls,
dropdownPrefixCls,
mergedColumns: rawMergedColumns,
@ -230,15 +241,11 @@ function useFilter<RecordType>({
getPopupContainer,
locale: tableLocale,
rootClassName,
}: FilterConfig<RecordType>): [
TransformColumns<RecordType>,
FilterState<RecordType>[],
Record<string, FilterValue | null>,
] {
} = props;
const warning = devUseWarning('Table');
const mergedColumns = React.useMemo(
() => getMergedColumns(rawMergedColumns || []),
() => getMergedColumns<RecordType>(rawMergedColumns || []),
[rawMergedColumns],
);
@ -291,13 +298,16 @@ function useFilter<RecordType>({
return collectedStates;
}, [mergedColumns, filterStates]);
const filters = React.useMemo(() => generateFilterInfo(mergedFilterStates), [mergedFilterStates]);
const filters = React.useMemo(
() => generateFilterInfo<RecordType>(mergedFilterStates),
[mergedFilterStates],
);
const triggerFilter = (filterState: FilterState<RecordType>) => {
const newFilterStates = mergedFilterStates.filter(({ key }) => key !== filterState.key);
newFilterStates.push(filterState);
setFilterStates(newFilterStates);
onFilterChange(generateFilterInfo(newFilterStates), newFilterStates);
onFilterChange(generateFilterInfo<RecordType>(newFilterStates), newFilterStates);
};
const transformColumns = (innerColumns: ColumnsType<RecordType>) =>
@ -313,8 +323,8 @@ function useFilter<RecordType>({
rootClassName,
);
return [transformColumns, mergedFilterStates, filters];
}
return [transformColumns, mergedFilterStates, filters] as const;
};
export { flattenKeys };

View File

@ -1,19 +1,20 @@
import * as React from 'react';
import type { AnyObject } from '../../_util/type';
import type { GetRowKey, Key } from '../interface';
interface MapCache<RecordType> {
interface MapCache<RecordType extends AnyObject = AnyObject> {
data?: readonly RecordType[];
childrenColumnName?: string;
kvMap?: Map<Key, RecordType>;
getRowKey?: (record: RecordType, index: number) => Key;
}
export default function useLazyKVMap<RecordType>(
const useLazyKVMap = <RecordType extends AnyObject = AnyObject>(
data: readonly RecordType[],
childrenColumnName: string,
getRowKey: GetRowKey<RecordType>,
) {
) => {
const mapCacheRef = React.useRef<MapCache<RecordType>>({});
function getRecordByKey(key: Key): RecordType {
@ -48,8 +49,10 @@ export default function useLazyKVMap<RecordType>(
};
}
return mapCacheRef.current.kvMap!.get(key)!;
return mapCacheRef.current.kvMap?.get(key)!;
}
return [getRecordByKey];
}
return [getRecordByKey] as const;
};
export default useLazyKVMap;

View File

@ -4,6 +4,7 @@ import CaretUpOutlined from '@ant-design/icons/CaretUpOutlined';
import classNames from 'classnames';
import KeyCode from 'rc-util/lib/KeyCode';
import type { AnyObject } from '../../_util/type';
import type { TooltipProps } from '../../tooltip';
import Tooltip from '../../tooltip';
import type {
@ -24,16 +25,18 @@ import { getColumnKey, getColumnPos, renderColumnTitle, safeColumnTitle } from '
const ASCEND = 'ascend';
const DESCEND = 'descend';
function getMultiplePriority<RecordType>(column: ColumnType<RecordType>): number | false {
const getMultiplePriority = <RecordType extends AnyObject = AnyObject>(
column: ColumnType<RecordType>,
): number | false => {
if (typeof column.sorter === 'object' && typeof column.sorter.multiple === 'number') {
return column.sorter.multiple;
}
return false;
}
};
function getSortFunction<RecordType>(
const getSortFunction = <RecordType extends AnyObject = AnyObject>(
sorter: ColumnType<RecordType>['sorter'],
): CompareFn<RecordType> | false {
): CompareFn<RecordType> | false => {
if (typeof sorter === 'function') {
return sorter;
}
@ -41,42 +44,40 @@ function getSortFunction<RecordType>(
return sorter.compare;
}
return false;
}
};
function nextSortDirection(sortDirections: SortOrder[], current: SortOrder | null) {
const nextSortDirection = (sortDirections: SortOrder[], current: SortOrder | null) => {
if (!current) {
return sortDirections[0];
}
return sortDirections[sortDirections.indexOf(current) + 1];
}
};
export interface SortState<RecordType> {
export interface SortState<RecordType extends AnyObject = AnyObject> {
column: ColumnType<RecordType>;
key: Key;
sortOrder: SortOrder | null;
multiplePriority: number | false;
}
function collectSortStates<RecordType>(
const collectSortStates = <RecordType extends AnyObject = AnyObject>(
columns: ColumnsType<RecordType>,
init: boolean,
pos?: string,
): SortState<RecordType>[] {
): SortState<RecordType>[] => {
let sortStates: SortState<RecordType>[] = [];
function pushState(column: ColumnsType<RecordType>[number], columnPos: string) {
const pushState = (column: ColumnsType<RecordType>[number], columnPos: string) => {
sortStates.push({
column,
key: getColumnKey(column, columnPos),
multiplePriority: getMultiplePriority(column),
key: getColumnKey<RecordType>(column, columnPos),
multiplePriority: getMultiplePriority<RecordType>(column),
sortOrder: column.sortOrder!,
});
}
};
(columns || []).forEach((column, index) => {
const columnPos = getColumnPos(index, pos);
if ((column as ColumnGroupType<RecordType>).children) {
if ('sortOrder' in column) {
// Controlled
@ -84,7 +85,11 @@ function collectSortStates<RecordType>(
}
sortStates = [
...sortStates,
...collectSortStates((column as ColumnGroupType<RecordType>).children, init, columnPos),
...collectSortStates<RecordType>(
(column as ColumnGroupType<RecordType>).children,
init,
columnPos,
),
];
} else if (column.sorter) {
if ('sortOrder' in column) {
@ -95,7 +100,7 @@ function collectSortStates<RecordType>(
sortStates.push({
column,
key: getColumnKey(column, columnPos),
multiplePriority: getMultiplePriority(column),
multiplePriority: getMultiplePriority<RecordType>(column),
sortOrder: column.defaultSortOrder!,
});
}
@ -103,9 +108,9 @@ function collectSortStates<RecordType>(
});
return sortStates;
}
};
function injectSorter<RecordType>(
const injectSorter = <RecordType extends AnyObject = AnyObject>(
prefixCls: string,
columns: ColumnsType<RecordType>,
sorterStates: SortState<RecordType>[],
@ -114,11 +119,10 @@ function injectSorter<RecordType>(
tableLocale?: TableLocale,
tableShowSorterTooltip?: boolean | SorterTooltipProps,
pos?: string,
): ColumnsType<RecordType> {
return (columns || []).map((column, index) => {
): ColumnsType<RecordType> => {
const finalColumns = (columns || []).map((column, index) => {
const columnPos = getColumnPos(index, pos);
let newColumn: ColumnsType<RecordType>[number] = column;
if (newColumn.sorter) {
const sortDirections: SortOrder[] = newColumn.sortDirections || defaultSortDirections;
const showSorterTooltip =
@ -220,7 +224,7 @@ function injectSorter<RecordType>(
column,
key: columnKey,
sortOrder: nextSortOrder,
multiplePriority: getMultiplePriority(column),
multiplePriority: getMultiplePriority<RecordType>(column),
});
originOnClick?.(event);
};
@ -230,7 +234,7 @@ function injectSorter<RecordType>(
column,
key: columnKey,
sortOrder: nextSortOrder,
multiplePriority: getMultiplePriority(column),
multiplePriority: getMultiplePriority<RecordType>(column),
});
originOKeyDown?.(event);
}
@ -273,9 +277,10 @@ function injectSorter<RecordType>(
return newColumn;
});
}
return finalColumns;
};
const stateToInfo = <RecordType extends any>(
const stateToInfo = <RecordType extends AnyObject = AnyObject>(
sorterStates: SortState<RecordType>,
): SorterResult<RecordType> => {
const { column, sortOrder } = sorterStates;
@ -287,7 +292,7 @@ const stateToInfo = <RecordType extends any>(
};
};
const generateSorterInfo = <RecordType extends any>(
const generateSorterInfo = <RecordType extends AnyObject = AnyObject>(
sorterStates: SortState<RecordType>[],
): SorterResult<RecordType> | SorterResult<RecordType>[] => {
const list = sorterStates
@ -311,11 +316,11 @@ const generateSorterInfo = <RecordType extends any>(
return list;
};
export function getSortData<RecordType>(
export const getSortData = <RecordType extends AnyObject = AnyObject>(
data: readonly RecordType[],
sortStates: SortState<RecordType>[],
childrenColumnName: string,
): RecordType[] {
): RecordType[] => {
const innerSorterStates = sortStates
.slice()
.sort((a, b) => (b.multiplePriority as number) - (a.multiplePriority as number));
@ -323,7 +328,7 @@ export function getSortData<RecordType>(
const cloneData = data.slice();
const runningSorters = innerSorterStates.filter(
({ column: { sorter }, sortOrder }) => getSortFunction(sorter) && sortOrder,
({ column: { sorter }, sortOrder }) => getSortFunction<RecordType>(sorter) && sortOrder,
);
// Skip if no sorter needed
@ -340,7 +345,7 @@ export function getSortData<RecordType>(
sortOrder,
} = sorterState;
const compareFn = getSortFunction(sorter);
const compareFn = getSortFunction<RecordType>(sorter);
if (compareFn && sortOrder) {
const compareResult = compareFn(record1, record2, sortOrder);
@ -354,18 +359,18 @@ export function getSortData<RecordType>(
return 0;
})
.map<RecordType>((record) => {
const subRecords = (record as any)[childrenColumnName];
const subRecords = record[childrenColumnName];
if (subRecords) {
return {
...record,
[childrenColumnName]: getSortData(subRecords, sortStates, childrenColumnName),
[childrenColumnName]: getSortData<RecordType>(subRecords, sortStates, childrenColumnName),
};
}
return record;
});
}
};
interface SorterConfig<RecordType> {
interface SorterConfig<RecordType extends AnyObject = AnyObject> {
prefixCls: string;
mergedColumns: ColumnsType<RecordType>;
onSorterChange: (
@ -377,28 +382,32 @@ interface SorterConfig<RecordType> {
showSorterTooltip?: boolean | SorterTooltipProps;
}
export default function useFilterSorter<RecordType>({
prefixCls,
mergedColumns,
onSorterChange,
sortDirections,
tableLocale,
showSorterTooltip,
}: SorterConfig<RecordType>): [
const useFilterSorter = <RecordType extends AnyObject = AnyObject>(
props: SorterConfig<RecordType>,
): [
TransformColumns<RecordType>,
SortState<RecordType>[],
ColumnTitleProps<RecordType>,
() => SorterResult<RecordType> | SorterResult<RecordType>[],
] {
] => {
const {
prefixCls,
mergedColumns,
sortDirections,
tableLocale,
showSorterTooltip,
onSorterChange,
} = props;
const [sortStates, setSortStates] = React.useState<SortState<RecordType>[]>(
collectSortStates(mergedColumns, true),
collectSortStates<RecordType>(mergedColumns, true),
);
const getColumnKeys = (columns: ColumnsType<RecordType>, pos?: string): Key[] => {
const newKeys: Key[] = [];
columns.forEach((item, index) => {
const columnPos = getColumnPos(index, pos);
newKeys.push(getColumnKey(item, columnPos));
newKeys.push(getColumnKey<RecordType>(item, columnPos));
if (Array.isArray((item as ColumnGroupType<RecordType>).children)) {
const childKeys = getColumnKeys((item as ColumnGroupType<RecordType>).children, columnPos);
newKeys.push(...childKeys);
@ -408,7 +417,7 @@ export default function useFilterSorter<RecordType>({
};
const mergedSorterStates = React.useMemo<SortState<RecordType>[]>(() => {
let validate = true;
const collectedStates = collectSortStates(mergedColumns, false);
const collectedStates = collectSortStates<RecordType>(mergedColumns, false);
// Return if not controlled
if (!collectedStates.length) {
@ -498,5 +507,7 @@ export default function useFilterSorter<RecordType>({
const getSorters = () => generateSorterInfo(mergedSorterStates);
return [transformColumns, mergedSorterStates, columnTitleSorterProps, getSorters];
}
return [transformColumns, mergedSorterStates, columnTitleSorterProps, getSorters] as const;
};
export default useFilterSorter;

View File

@ -1,32 +1,38 @@
import * as React from 'react';
import type { AnyObject } from 'antd/es/_util/type';
import type { ColumnsType, ColumnTitleProps, TransformColumns } from '../interface';
import type {
ColumnGroupType,
ColumnsType,
ColumnTitleProps,
ColumnType,
TransformColumns,
} from '../interface';
import { renderColumnTitle } from '../util';
function fillTitle<RecordType>(
const fillTitle = <RecordType extends AnyObject = AnyObject>(
columns: ColumnsType<RecordType>,
columnTitleProps: ColumnTitleProps<RecordType>,
) {
return columns.map((column) => {
const cloneColumn = { ...column };
) => {
const finalColumns = columns.map((column) => {
const cloneColumn: ColumnGroupType<RecordType> | ColumnType<RecordType> = { ...column };
cloneColumn.title = renderColumnTitle(column.title, columnTitleProps);
if ('children' in cloneColumn) {
cloneColumn.children = fillTitle(cloneColumn.children, columnTitleProps);
cloneColumn.children = fillTitle<RecordType>(cloneColumn.children, columnTitleProps);
}
return cloneColumn;
});
}
return finalColumns;
};
export default function useTitleColumns<RecordType>(
const useTitleColumns = <RecordType extends AnyObject = AnyObject>(
columnTitleProps: ColumnTitleProps<RecordType>,
): [TransformColumns<RecordType>] {
const filledColumns = React.useCallback(
(columns: ColumnsType<RecordType>) => fillTitle(columns, columnTitleProps),
) => {
const filledColumns = React.useCallback<TransformColumns<RecordType>>(
(columns) => fillTitle<RecordType>(columns, columnTitleProps),
[columnTitleProps],
);
return [filledColumns] as const;
};
return [filledColumns];
}
export default useTitleColumns;

View File

@ -66,9 +66,10 @@ export type SorterTooltipProps = TooltipProps & {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const TableActions = ['paginate', 'sort', 'filter'] as const;
export type TableAction = (typeof TableActions)[number];
export type CompareFn<T> = (a: T, b: T, sortOrder?: SortOrder) => number;
export type CompareFn<T = AnyObject> = (a: T, b: T, sortOrder?: SortOrder) => number;
export interface ColumnFilterItem {
text: React.ReactNode;
@ -76,7 +77,7 @@ export interface ColumnFilterItem {
children?: ColumnFilterItem[];
}
export interface ColumnTitleProps<RecordType> {
export interface ColumnTitleProps<RecordType = AnyObject> {
/** @deprecated Please use `sorterColumns` instead. */
sortOrder?: SortOrder;
/** @deprecated Please use `sorterColumns` instead. */
@ -86,7 +87,7 @@ export interface ColumnTitleProps<RecordType> {
filters?: Record<string, FilterValue>;
}
export type ColumnTitle<RecordType> =
export type ColumnTitle<RecordType = AnyObject> =
| React.ReactNode
| ((props: ColumnTitleProps<RecordType>) => React.ReactNode);
@ -115,7 +116,8 @@ export interface FilterDropdownProps {
visible: boolean;
}
export interface ColumnType<RecordType> extends Omit<RcColumnType<RecordType>, 'title'> {
export interface ColumnType<RecordType = AnyObject>
extends Omit<RcColumnType<RecordType>, 'title'> {
title?: ColumnTitle<RecordType>;
// Sorter
sorter?:
@ -158,11 +160,12 @@ export interface ColumnType<RecordType> extends Omit<RcColumnType<RecordType>, '
onFilterDropdownVisibleChange?: (visible: boolean) => void;
}
export interface ColumnGroupType<RecordType> extends Omit<ColumnType<RecordType>, 'dataIndex'> {
export interface ColumnGroupType<RecordType = AnyObject>
extends Omit<ColumnType<RecordType>, 'dataIndex'> {
children: ColumnsType<RecordType>;
}
export type ColumnsType<RecordType = any> = (
export type ColumnsType<RecordType = AnyObject> = (
| ColumnGroupType<RecordType>
| ColumnType<RecordType>
)[];
@ -173,7 +176,7 @@ export interface SelectionItem {
onSelect?: SelectionItemSelectFn;
}
export type SelectionSelectFn<T> = (
export type SelectionSelectFn<T = AnyObject> = (
record: T,
selected: boolean,
selectedRows: T[],
@ -182,7 +185,7 @@ export type SelectionSelectFn<T> = (
export type RowSelectMethod = 'all' | 'none' | 'invert' | 'single' | 'multiple';
export interface TableRowSelection<T> {
export interface TableRowSelection<T = AnyObject> {
/** Keep the selection keys in list even the key not exist in `dataSource` anymore */
preserveSelectedRowKeys?: boolean;
type?: RowSelectionType;
@ -214,16 +217,16 @@ export interface TableRowSelection<T> {
onCell?: GetComponentProps<T>;
}
export type TransformColumns<RecordType> = (
export type TransformColumns<RecordType = AnyObject> = (
columns: ColumnsType<RecordType>,
) => ColumnsType<RecordType>;
export interface TableCurrentDataSource<RecordType> {
export interface TableCurrentDataSource<RecordType = AnyObject> {
currentDataSource: RecordType[];
action: TableAction;
}
export interface SorterResult<RecordType> {
export interface SorterResult<RecordType = AnyObject> {
column?: ColumnType<RecordType>;
order?: SortOrder;
field?: Key | readonly Key[];

View File

@ -1,31 +1,33 @@
/* eslint-disable import/prefer-default-export */
import type { AnyObject } from '../_util/type';
import type { ColumnTitle, ColumnTitleProps, ColumnType, Key } from './interface';
export function getColumnKey<RecordType>(column: ColumnType<RecordType>, defaultKey: string): Key {
export const getColumnKey = <RecordType extends AnyObject = AnyObject>(
column: ColumnType<RecordType>,
defaultKey: string,
): Key => {
if ('key' in column && column.key !== undefined && column.key !== null) {
return column.key;
}
if (column.dataIndex) {
return (Array.isArray(column.dataIndex) ? column.dataIndex.join('.') : column.dataIndex) as Key;
return Array.isArray(column.dataIndex) ? column.dataIndex.join('.') : (column.dataIndex as Key);
}
return defaultKey;
}
};
export function getColumnPos(index: number, pos?: string) {
return pos ? `${pos}-${index}` : `${index}`;
}
export function renderColumnTitle<RecordType>(
export const renderColumnTitle = <RecordType extends AnyObject = AnyObject>(
title: ColumnTitle<RecordType>,
props: ColumnTitleProps<RecordType>,
) {
) => {
if (typeof title === 'function') {
return title(props);
}
return title;
}
};
/**
* Safe get column title
@ -35,11 +37,13 @@ export function renderColumnTitle<RecordType>(
* @param title
* @returns
*/
export function safeColumnTitle<RecordType>(
export const safeColumnTitle = <RecordType extends AnyObject = AnyObject>(
title: ColumnTitle<RecordType>,
props: ColumnTitleProps<RecordType>,
) {
const res = renderColumnTitle(title, props);
if (Object.prototype.toString.call(res) === '[object Object]') return '';
) => {
const res = renderColumnTitle<RecordType>(title, props);
if (Object.prototype.toString.call(res) === '[object Object]') {
return '';
}
return res;
}
};

View File

@ -3,7 +3,7 @@ import { ConfigProvider, Space, Switch, Table, Tag, Transfer } from 'antd';
import type { GetProp, TableColumnsType, TableProps, TransferProps } from 'antd';
import difference from 'lodash/difference';
type TableRowSelection<T> = TableProps<T>['rowSelection'];
type TableRowSelection<T extends object = object> = TableProps<T>['rowSelection'];
type TransferItem = GetProp<TransferProps, 'dataSource'>[number];