feat: Support rowSelection.dirty (#24718)

* feat: Support rowSelection.dirty

* rename to reserveKeys

* preserveKeys will keep record also

* to preserveSelectedRowKeys
This commit is contained in:
二货机器人 2020-06-04 17:08:20 +08:00 committed by GitHub
parent 71f4a2f985
commit d154435291
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 128 additions and 59 deletions

View File

@ -787,27 +787,56 @@ describe('Table.rowSelection', () => {
expect(onChange.mock.calls[0][1]).toEqual([expect.objectContaining({ name: 'bamboo' })]);
});
it('do not cache selected keys', () => {
const onChange = jest.fn();
const wrapper = mount(
<Table
dataSource={[{ name: 'light' }, { name: 'bamboo' }]}
rowSelection={{ onChange }}
rowKey="name"
/>,
);
describe('cache with selected keys', () => {
it('default not cache', () => {
const onChange = jest.fn();
const wrapper = mount(
<Table
dataSource={[{ name: 'light' }, { name: 'bamboo' }]}
rowSelection={{ onChange }}
rowKey="name"
/>,
);
wrapper
.find('tbody input')
.first()
.simulate('change', { target: { checked: true } });
expect(onChange).toHaveBeenCalledWith(['light'], [{ name: 'light' }]);
wrapper
.find('tbody input')
.first()
.simulate('change', { target: { checked: true } });
expect(onChange).toHaveBeenCalledWith(['light'], [{ name: 'light' }]);
wrapper.setProps({ dataSource: [{ name: 'bamboo' }] });
wrapper
.find('tbody input')
.first()
.simulate('change', { target: { checked: true } });
expect(onChange).toHaveBeenCalledWith(['bamboo'], [{ name: 'bamboo' }]);
wrapper.setProps({ dataSource: [{ name: 'bamboo' }] });
wrapper
.find('tbody input')
.first()
.simulate('change', { target: { checked: true } });
expect(onChange).toHaveBeenCalledWith(['bamboo'], [{ name: 'bamboo' }]);
});
it('cache with preserveSelectedRowKeys', () => {
const onChange = jest.fn();
const wrapper = mount(
<Table
dataSource={[{ name: 'light' }, { name: 'bamboo' }]}
rowSelection={{ onChange, preserveSelectedRowKeys: true }}
rowKey="name"
/>,
);
wrapper
.find('tbody input')
.first()
.simulate('change', { target: { checked: true } });
expect(onChange).toHaveBeenCalledWith(['light'], [{ name: 'light' }]);
wrapper.setProps({ dataSource: [{ name: 'bamboo' }] });
wrapper
.find('tbody input')
.first()
.simulate('change', { target: { checked: true } });
expect(onChange).toHaveBeenCalledWith(
['light', 'bamboo'],
[{ name: 'light' }, { name: 'bamboo' }],
);
});
});
});

View File

@ -11,12 +11,16 @@ title:
另外,本例也展示了筛选排序功能如何交给服务端实现,列不需要指定具体的 `onFilter``sorter` 函数,而是在把筛选和排序的参数发到服务端来处理。
当使用 `rowSelection` 时,请设置 `rowSelection.preserveSelectedRowKeys` 属性以保留 `key`
**注意,此示例使用 [模拟接口](https://randomuser.me),展示数据可能不准确,请打开网络面板查看请求。**
## en-US
This example shows how to fetch and present data from a remote server, and how to implement filtering and sorting in server side by sending related parameters to server.
Setting `rowSelection.preserveSelectedRowKeys` to keep the `key` when enable selection.
**Note, this example use [Mock API](https://randomuser.me) that you can look up in Network Console.**
```jsx

View File

@ -1,4 +1,5 @@
import * as React from 'react';
import { useState, useCallback, useMemo } from 'react';
import DownOutlined from '@ant-design/icons/DownOutlined';
import { INTERNAL_COL_DEFINE } from 'rc-table';
import { FixedType } from 'rc-table/lib/interface';
@ -71,6 +72,7 @@ export default function useSelection<RecordType>(
config: UseSelectionConfig<RecordType>,
): [TransformColumns<RecordType>, Set<Key>] {
const {
preserveSelectedRowKeys,
selectedRowKeys,
getCheckboxProps,
onChange: onSelectionChange,
@ -99,15 +101,19 @@ export default function useSelection<RecordType>(
getPopupContainer,
} = config;
const [innerSelectedKeys, setInnerSelectedKeys] = React.useState<Key[]>();
// ======================== Caches ========================
const preserveRecordsRef = React.useRef(new Map<Key, RecordType>());
// ========================= Keys =========================
const [innerSelectedKeys, setInnerSelectedKeys] = useState<Key[]>();
const mergedSelectedKeys = selectedRowKeys || innerSelectedKeys || EMPTY_LIST;
const mergedSelectedKeySet = React.useMemo(() => {
const mergedSelectedKeySet = useMemo(() => {
const keys = selectionType === 'radio' ? mergedSelectedKeys.slice(0, 1) : mergedSelectedKeys;
return new Set(keys);
}, [mergedSelectedKeys, selectionType]);
// Save last selected key to enable range selection
const [lastSelectedKey, setLastSelectedKey] = React.useState<Key | null>(null);
const [lastSelectedKey, setLastSelectedKey] = useState<Key | null>(null);
// Reset if rowSelection reset
React.useEffect(() => {
@ -116,18 +122,42 @@ export default function useSelection<RecordType>(
}
}, [!!rowSelection]);
const setSelectedKeys = React.useCallback(
const setSelectedKeys = useCallback(
(keys: Key[]) => {
const availableKeys: Key[] = [];
const records: RecordType[] = [];
let availableKeys: Key[];
let records: RecordType[];
keys.forEach(key => {
const record = getRecordByKey(key);
if (record !== undefined) {
availableKeys.push(key);
records.push(record);
}
});
if (preserveSelectedRowKeys) {
// Keep key if mark as preserveSelectedRowKeys
const newCache = new Map<Key, RecordType>();
availableKeys = keys;
records = keys.map(key => {
let record = getRecordByKey(key);
if (!record && preserveRecordsRef.current.has(key)) {
record = preserveRecordsRef.current.get(key)!;
}
newCache.set(key, record);
return record;
});
// Refresh to new cache
preserveRecordsRef.current = newCache;
} else {
// Filter key which not exist in the `dataSource`
availableKeys = [];
records = [];
keys.forEach(key => {
const record = getRecordByKey(key);
if (record !== undefined) {
availableKeys.push(key);
records.push(record);
}
});
}
setInnerSelectedKeys(availableKeys);
@ -135,11 +165,12 @@ export default function useSelection<RecordType>(
onSelectionChange(availableKeys, records);
}
},
[setInnerSelectedKeys, getRecordByKey, onSelectionChange],
[setInnerSelectedKeys, getRecordByKey, onSelectionChange, preserveSelectedRowKeys],
);
// ====================== Selections ======================
// Trigger single `onSelect` event
const triggerSingleSelection = React.useCallback(
const triggerSingleSelection = useCallback(
(key: Key, selected: boolean, keys: Key[], event: Event) => {
if (onSelect) {
const rows = keys.map(k => getRecordByKey(k));
@ -151,7 +182,7 @@ export default function useSelection<RecordType>(
[onSelect, getRecordByKey, setSelectedKeys],
);
const mergedSelections = React.useMemo<SelectionItem[] | null>(() => {
const mergedSelections = useMemo<SelectionItem[] | null>(() => {
if (!selections || hideSelectAll) {
return null;
}
@ -202,7 +233,8 @@ export default function useSelection<RecordType>(
});
}, [selections, mergedSelectedKeySet, pageData, getRowKey]);
const transformColumns = React.useCallback(
// ======================= Columns ========================
const transformColumns = useCallback(
(columns: ColumnsType<RecordType>): ColumnsType<RecordType> => {
if (!rowSelection) {
return columns;

View File

@ -185,19 +185,20 @@ Properties for row selection.
| Property | Description | Type | Default | Version |
| --- | --- | --- | --- | --- |
| columnWidth | Set the width of the selection column | string\|number | `60px` | 4.0 |
| columnTitle | Set the title of the selection column | string\|React.ReactNode | - | 4.0 |
| fixed | Fixed selection column on the left | boolean | - | 4.0 |
| getCheckboxProps | Get Checkbox or Radio props | Function(record) | - | 4.0 |
| columnWidth | Set the width of the selection column | string\|number | `60px` | |
| columnTitle | Set the title of the selection column | string\|React.ReactNode | - | |
| fixed | Fixed selection column on the left | boolean | - | |
| getCheckboxProps | Get Checkbox or Radio props | Function(record) | - | |
| hideSelectAll | Hide the selectAll checkbox and custom selection | boolean | `false` | 4.3 |
| preserveSelectedRowKeys | Keep selection `key` even when it removed from `dataSource` | boolean | - | 4.4 |
| renderCell | Renderer of the table cell. Same as `render` in column | Function(checked, record, index, originNode) {} | - | 4.1 |
| selectedRowKeys | Controlled selected row keys | string\[]\|number[] | \[] | 4.0 |
| selections | Custom selection [config](#rowSelection), only displays default selections when set to `true` | object\[]\|boolean | - | 4.0 |
| type | `checkbox` or `radio` | `checkbox` \| `radio` | `checkbox` | 4.0 |
| onChange | Callback executed when selected rows change | Function(selectedRowKeys, selectedRows) | - | 4.0 |
| onSelect | Callback executed when select/deselect one row | Function(record, selected, selectedRows, nativeEvent) | - | 4.0 |
| onSelectAll | Callback executed when select/deselect all rows | Function(selected, selectedRows, changeRows) | - | 4.0 |
| onSelectInvert | Callback executed when row selection is inverted | Function(selectedRowKeys) | - | 4.0 |
| selectedRowKeys | Controlled selected row keys | string\[]\|number[] | \[] | |
| selections | Custom selection [config](#rowSelection), only displays default selections when set to `true` | object\[]\|boolean | - | |
| type | `checkbox` or `radio` | `checkbox` \| `radio` | `checkbox` | |
| onChange | Callback executed when selected rows change | Function(selectedRowKeys, selectedRows) | - | |
| onSelect | Callback executed when select/deselect one row | Function(record, selected, selectedRows, nativeEvent) | - | |
| onSelectAll | Callback executed when select/deselect all rows | Function(selected, selectedRows, changeRows) | - | |
| onSelectInvert | Callback executed when row selection is inverted | Function(selectedRowKeys) | - | |
### scroll

View File

@ -190,19 +190,20 @@ const columns = [
| 参数 | 说明 | 类型 | 默认值 | 版本 |
| --- | --- | --- | --- | --- |
| columnWidth | 自定义列表选择框宽度 | string\|number | `60px` | 4.0 |
| columnTitle | 自定义列表选择框标题 | string\|React.ReactNode | - | 4.0 |
| fixed | 把选择框列固定在左边 | boolean | - | 4.0 |
| getCheckboxProps | 选择框的默认属性配置 | Function(record) | - | 4.0 |
| columnWidth | 自定义列表选择框宽度 | string\|number | `60px` | |
| columnTitle | 自定义列表选择框标题 | string\|React.ReactNode | - | |
| fixed | 把选择框列固定在左边 | boolean | - | |
| getCheckboxProps | 选择框的默认属性配置 | Function(record) | - | |
| hideSelectAll | 隐藏全选勾选框与自定义选择项 | boolean | false | 4.3 |
| preserveSelectedRowKeys | 当数据被删除时仍然保留选项的 `key` | boolean | - | 4.4 |
| renderCell | 渲染勾选框,用法与 Column 的 `render` 相同 | Function(checked, record, index, originNode) {} | - | 4.1 |
| selectedRowKeys | 指定选中项的 key 数组,需要和 onChange 进行配合 | string\[]\|number[] | \[] | 4.0 |
| selections | 自定义选择项 [配置项](#selection), 设为 `true` 时使用默认选择项 | object\[]\|boolean | true | 4.0 |
| type | 多选/单选,`checkbox` or `radio` | string | `checkbox` | 4.0 |
| onChange | 选中项发生变化时的回调 | Function(selectedRowKeys, selectedRows) | - | 4.0 |
| onSelect | 用户手动选择/取消选择某行的回调 | Function(record, selected, selectedRows, nativeEvent) | - | 4.0 |
| onSelectAll | 用户手动选择/取消选择所有行的回调 | Function(selected, selectedRows, changeRows) | - | 4.0 |
| onSelectInvert | 用户手动选择反选的回调 | Function(selectedRowKeys) | - | 4.0 |
| selectedRowKeys | 指定选中项的 key 数组,需要和 onChange 进行配合 | string\[]\|number[] | \[] | |
| selections | 自定义选择项 [配置项](#selection), 设为 `true` 时使用默认选择项 | object\[]\|boolean | true | |
| type | 多选/单选,`checkbox` or `radio` | string | `checkbox` | |
| onChange | 选中项发生变化时的回调 | Function(selectedRowKeys, selectedRows) | - | |
| onSelect | 用户手动选择/取消选择某行的回调 | Function(record, selected, selectedRows, nativeEvent) | - | |
| onSelectAll | 用户手动选择/取消选择所有行的回调 | Function(selected, selectedRows, changeRows) | - | |
| onSelectInvert | 用户手动选择反选的回调 | Function(selectedRowKeys) | - | |
### scroll

View File

@ -125,6 +125,8 @@ export type SelectionSelectFn<T> = (
) => void;
export interface TableRowSelection<T> {
/** Keep the selection keys in list even the key not exist in `dataSource` anymore */
preserveSelectedRowKeys?: boolean;
type?: RowSelectionType;
selectedRowKeys?: Key[];
onChange?: (selectedRowKeys: Key[], selectedRows: T[]) => void;