Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(table): 修复树状表格多选时父子层级之间的联动交互效果问题 #1012

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions src/hooks/useDeepCompareMemo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import React, { useMemo } from 'react';
import { useDeepCompareMemoize } from './useDeepCompareMemoize';

export function useDeepCompareMemo<T>(factory: () => T, deps: React.DependencyList) {
// eslint-disable-next-line react-hooks/exhaustive-deps
return useMemo(factory, useDeepCompareMemoize(deps));
}
12 changes: 12 additions & 0 deletions src/hooks/useDeepCompareMemoize.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import React from 'react';
import isEqual from 'lodash/isEqual';

export function useDeepCompareMemoize(value: React.DependencyList) {
const ref = React.useRef<React.DependencyList>([]);

if (!isEqual(value, ref.current)) {
ref.current = value;
}

return ref.current;
}
63 changes: 48 additions & 15 deletions src/table/hooks/useRowSelect.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// 行选中相关功能:单选 + 多选

import React, { useEffect, useState, MouseEvent } from 'react';
import React, { useEffect, useState, MouseEvent, useMemo } from 'react';
import intersection from 'lodash/intersection';
import get from 'lodash/get';
import isFunction from 'lodash/isFunction';
Expand All @@ -11,26 +11,43 @@ import {
RowClassNameParams,
TableRowData,
TdBaseTableProps,
TdPrimaryTableProps,
TdEnhancedTableProps,
} from '../type';
import { filterDataByIds, isRowSelectedDisabled } from '../utils';
import useClassName from './useClassName';
import Checkbox from '../../checkbox';
import Radio from '../../radio';
import { ClassName } from '../../common';
import log from '../../_common/js/log';
import { useDeepCompareMemo } from '../../hooks/useDeepCompareMemo';

export default function useRowSelect(props: TdPrimaryTableProps) {
const { selectedRowKeys, columns, data, rowKey } = props;
export default function useRowSelect(props: TdEnhancedTableProps) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

useRowSelect 是 PrimaryTable 的行选中功能,对于这类组件,并没有 EnhancedTable 相关的属性。当前改动不合适

const { selectedRowKeys, columns, data, rowKey, tree } = props;
const { tableSelectedClasses } = useClassName();
const [selectedRowClassNames, setSelectedRowClassNames] = useState<TdBaseTableProps['rowClassName']>();
const [tSelectedRowKeys, setTSelectedRowKeys] = useControlled(props, 'selectedRowKeys', props.onSelectChange);
const selectColumn = props.columns.find(({ type }) => ['multiple', 'single'].includes(type));
const canSelectedRows = props.data.filter((row, rowIndex): boolean => !isDisabled(row, rowIndex));

const rowDataKeys = useMemo(
() => ({
rowKey: rowKey || 'id',
childrenKey: tree?.childrenKey || 'children',
}),
[rowKey, tree?.childrenKey],
);

// 是否为树状表格
const isTree = tree && Object.keys(tree).length;

const canSelectedRows = useDeepCompareMemo(
() => data.filter((row, rowIndex): boolean => !isDisabled(row, rowIndex)),
[data, isTree],
);

// 选中的行,和所有可以选择的行,交集,用于计算 isSelectedAll 和 isIndeterminate
const intersectionKeys = intersection(
tSelectedRowKeys,
canSelectedRows.map((t) => get(t, rowKey || 'id')),
canSelectedRows.map((t) => get(t, rowDataKeys.rowKey)),
);

useEffect(
Expand All @@ -41,7 +58,7 @@ export default function useRowSelect(props: TdPrimaryTableProps) {
const disabledRowClass = selectColumn?.disabled ? disabledRowFunc : undefined;
const selected = new Set(tSelectedRowKeys);
const selectedRowClassFunc = ({ row }: RowClassNameParams<TableRowData>) => {
const rowId = get(row, rowKey || 'id');
const rowId = get(row, rowDataKeys.rowKey);
return selected.has(rowId) ? tableSelectedClasses.selected : '';
};
const selectedRowClass = selected.size ? selectedRowClassFunc : undefined;
Expand All @@ -57,6 +74,7 @@ export default function useRowSelect(props: TdPrimaryTableProps) {

function getSelectedHeader() {
const isIndeterminate = intersectionKeys.length > 0 && intersectionKeys.length < canSelectedRows.length;

return () => (
<Checkbox
checked={intersectionKeys.length === canSelectedRows.length}
Expand All @@ -69,10 +87,26 @@ export default function useRowSelect(props: TdPrimaryTableProps) {

function renderSelectCell(p: PrimaryTableCellParams<TableRowData>) {
const { col: column, row = {}, rowIndex } = p;
const checked = tSelectedRowKeys.includes(get(row, rowKey || 'id'));
let checked = tSelectedRowKeys.includes(get(row, rowDataKeys.rowKey));
const disabled: boolean =
typeof column.disabled === 'function' ? column.disabled({ row, rowIndex }) : column.disabled;
const checkProps = isFunction(column.checkProps) ? column.checkProps({ row, rowIndex }) : column.checkProps;
// 树状选择状态处理
let childIsIndeterminate;
if (isTree) {
const childCanSelectedRows = (get(row, rowDataKeys.childrenKey) || []).filter(
(r, rIndex) => !isDisabled(r, rIndex),
);
const childIntersectionKeys = intersection(
tSelectedRowKeys,
childCanSelectedRows.map((t) => get(t, rowDataKeys.rowKey)),
);
childIsIndeterminate =
childIntersectionKeys.length > 0 && childIntersectionKeys.length < childCanSelectedRows.length;
if (childIntersectionKeys.length === childCanSelectedRows.length && childIntersectionKeys.length > 0) {
checked = true;
}
}
const selectBoxProps = {
checked,
disabled,
Expand All @@ -86,14 +120,14 @@ export default function useRowSelect(props: TdPrimaryTableProps) {
e?.stopPropagation();
};
if (column.type === 'single') return <Radio {...selectBoxProps} onClick={onCheckClick} />;
if (column.type === 'multiple') return <Checkbox {...selectBoxProps} onClick={onCheckClick} />;
if (column.type === 'multiple')
return <Checkbox {...selectBoxProps} indeterminate={childIsIndeterminate} onClick={onCheckClick} />;
return null;
}

function handleSelectChange(row: TableRowData = {}) {
let selectedRowKeys = [...tSelectedRowKeys];
const reRowKey = rowKey || 'id';
const id = get(row, reRowKey);
const id = get(row, rowDataKeys.rowKey);
const selectedRowIndex = selectedRowKeys.indexOf(id);
const isExisted = selectedRowIndex !== -1;
if (selectColumn.type === 'multiple') {
Expand All @@ -105,20 +139,19 @@ export default function useRowSelect(props: TdPrimaryTableProps) {
return;
}
setTSelectedRowKeys(selectedRowKeys, {
selectedRowData: filterDataByIds(props.data, selectedRowKeys, reRowKey),
selectedRowData: filterDataByIds(props.data, selectedRowKeys, rowDataKeys.rowKey),
currentRowKey: id,
currentRowData: row,
type: isExisted ? 'uncheck' : 'check',
});
}

function handleSelectAll(checked: boolean) {
const reRowKey = rowKey || 'id';
const canSelectedRowKeys = canSelectedRows.map((record) => get(record, reRowKey));
const canSelectedRowKeys = canSelectedRows.map((record) => get(record, rowDataKeys.rowKey));
const disabledSelectedRowKeys = selectedRowKeys?.filter((id) => !canSelectedRowKeys.includes(id)) || [];
const allIds = checked ? [...disabledSelectedRowKeys, ...canSelectedRowKeys] : [...disabledSelectedRowKeys];
setTSelectedRowKeys(allIds, {
selectedRowData: filterDataByIds(props.data, allIds, reRowKey),
selectedRowData: filterDataByIds(props.data, allIds, rowDataKeys.rowKey),
type: checked ? 'check' : 'uncheck',
currentRowKey: 'CHECK_ALL_BOX',
});
Expand Down
2 changes: 1 addition & 1 deletion src/table/hooks/useTreeSelect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import useControlled from '../../hooks/useControlled';
export const childrenMap = new Map();

export interface GetChildrenDataReturnValue {
allChildren: Array<any>;
allChildren: Array<TableRowData>;
allChildrenKeys: Array<string | number>;
}

Expand Down