perf(useTableBlockProps): avoid unnecessary re-renders

This commit is contained in:
Zeke Zhang 2024-11-03 14:46:12 +08:00
parent bca6d965ee
commit 8106424180
3 changed files with 103 additions and 65 deletions

View File

@ -25,6 +25,10 @@ TableBlockContext.displayName = 'TableBlockContext';
const TableBlockContextBasicValue = createContext<{
field: any;
rowKey: string;
dragSortBy?: string;
childrenColumnName?: string;
showIndex?: boolean;
dragSort?: boolean;
}>(null);
TableBlockContextBasicValue.displayName = 'TableBlockContextBasicValue';
@ -56,6 +60,7 @@ interface Props {
collection?: string;
children?: any;
expandFlag?: boolean;
dragSortBy?: string;
}
const InternalTableBlockProvider = (props: Props) => {
@ -100,8 +105,12 @@ const InternalTableBlockProvider = (props: Props) => {
() => ({
field,
rowKey,
childrenColumnName,
showIndex,
dragSort,
dragSortBy: props.dragSortBy,
}),
[field, rowKey],
[field, rowKey, childrenColumnName, showIndex, dragSort, props.dragSortBy],
);
// Keep the original for compatibility

View File

@ -11,8 +11,10 @@ import { ArrayField } from '@formily/core';
import { useField, useFieldSchema } from '@formily/react';
import { isEqual } from 'lodash';
import { useCallback, useEffect, useMemo, useRef } from 'react';
import { useTableBlockContext } from '../../../../../block-provider/TableBlockProvider';
import { useTableBlockContextBasicValue } from '../../../../../block-provider/TableBlockProvider';
import { findFilterTargets } from '../../../../../block-provider/hooks';
import { useDataBlockRequest } from '../../../../../data-source/data-block/DataBlockRequestProvider';
import { useDataBlockResource } from '../../../../../data-source/data-block/DataBlockResourceProvider';
import { DataBlock, useFilterBlock } from '../../../../../filter-provider/FilterProvider';
import { mergeFilter } from '../../../../../filter-provider/utils';
import { removeNullCondition } from '../../../../../schema-component';
@ -20,13 +22,15 @@ import { removeNullCondition } from '../../../../../schema-component';
export const useTableBlockProps = () => {
const field = useField<ArrayField>();
const fieldSchema = useFieldSchema();
const ctx = useTableBlockContext();
const resource = useDataBlockResource();
const service = useDataBlockRequest() as any;
const { getDataBlocks } = useFilterBlock();
const isLoading = ctx?.service?.loading;
const isLoading = service?.loading;
const tableBlockContextBasicValue = useTableBlockContextBasicValue();
const ctxRef = useRef(null);
ctxRef.current = ctx;
const meta = ctx?.service?.data?.meta || {};
ctxRef.current = { service, resource };
const meta = service?.data?.meta || {};
const pagination = useMemo(
() => ({
pageSize: meta?.pageSize,
@ -38,9 +42,9 @@ export const useTableBlockProps = () => {
useEffect(() => {
if (!isLoading) {
const serviceResponse = ctx?.service?.data;
const serviceResponse = service?.data;
const data = serviceResponse?.data || [];
const selectedRowKeys = ctx?.field?.data?.selectedRowKeys;
const selectedRowKeys = tableBlockContextBasicValue.field?.data?.selectedRowKeys;
if (!isEqual(field.value, data)) {
field.value = data;
@ -52,37 +56,43 @@ export const useTableBlockProps = () => {
field.data.selectedRowKeys = selectedRowKeys;
}
field.componentProps.pagination = pagination;
// 大概率是无用代码,且可能会影响性能
// field.componentProps.pagination = pagination;
}
}, [field, ctx?.service?.data, isLoading, ctx?.field?.data?.selectedRowKeys, pagination]);
}, [field, service?.data, isLoading, tableBlockContextBasicValue.field?.data?.selectedRowKeys]);
return {
defaultDataSource: ctx?.service?.data?.data || [],
bordered: ctx.bordered,
childrenColumnName: ctx.childrenColumnName,
loading: ctx?.service?.loading,
showIndex: ctx.showIndex,
dragSort: ctx.dragSort && ctx.dragSortBy,
rowKey: ctx.rowKey || fieldSchema?.['x-component-props']?.rowKey || 'id',
defaultDataSource: service?.data?.data || [],
childrenColumnName: tableBlockContextBasicValue.childrenColumnName,
loading: service?.loading,
showIndex: tableBlockContextBasicValue.showIndex,
dragSort: tableBlockContextBasicValue.dragSort && tableBlockContextBasicValue.dragSortBy,
rowKey: tableBlockContextBasicValue.rowKey || fieldSchema?.['x-component-props']?.rowKey || 'id',
pagination: fieldSchema?.['x-component-props']?.pagination === false ? false : pagination,
onRowSelectionChange: useCallback((selectedRowKeys, selectedRowData) => {
ctx.field.data = ctx?.field?.data || {};
ctx.field.data.selectedRowKeys = selectedRowKeys;
ctx.field.data.selectedRowData = selectedRowData;
ctx?.field?.onRowSelect?.(selectedRowKeys);
if (tableBlockContextBasicValue) {
tableBlockContextBasicValue.field.data = tableBlockContextBasicValue.field?.data || {};
tableBlockContextBasicValue.field.data.selectedRowKeys = selectedRowKeys;
tableBlockContextBasicValue.field.data.selectedRowData = selectedRowData;
tableBlockContextBasicValue.field?.onRowSelect?.(selectedRowKeys);
}
}, []),
onRowDragEnd: useCallback(
async ({ from, to }) => {
await ctx.resource.move({
sourceId: from[ctx.rowKey || 'id'],
targetId: to[ctx.rowKey || 'id'],
sortField: ctx.dragSort && ctx.dragSortBy,
await ctxRef.current.resource.move({
sourceId: from[tableBlockContextBasicValue.rowKey || 'id'],
targetId: to[tableBlockContextBasicValue.rowKey || 'id'],
sortField: tableBlockContextBasicValue.dragSort && tableBlockContextBasicValue.dragSortBy,
});
ctx.service.refresh();
ctxRef.current.service.refresh();
// ctx.resource
// eslint-disable-next-line react-hooks/exhaustive-deps
},
[ctx.rowKey, ctx.dragSort, ctx.dragSortBy],
[
tableBlockContextBasicValue.rowKey,
tableBlockContextBasicValue.dragSort,
tableBlockContextBasicValue.dragSortBy,
],
),
onChange: useCallback(
({ current, pageSize }, filters, sorter) => {
@ -91,7 +101,7 @@ export const useTableBlockProps = () => {
? sorter.order === `ascend`
? [sorter.field]
: [`-${sorter.field}`]
: globalSort || ctxRef.current.dragSortBy;
: globalSort || tableBlockContextBasicValue.dragSortBy;
const currentPageSize = pageSize || fieldSchema.parent?.['x-decorator-props']?.['params']?.pageSize;
const args = { ...ctxRef.current?.service?.params?.[0], page: current || 1, pageSize: currentPageSize };
if (sort) {
@ -122,14 +132,14 @@ export const useTableBlockProps = () => {
const isForeignKey = block.foreignKeyFields?.some((field) => field.name === target.field);
const sourceKey = getSourceKey(currentBlock, target.field);
const recordKey = isForeignKey ? sourceKey : ctx.rowKey;
const recordKey = isForeignKey ? sourceKey : tableBlockContextBasicValue.rowKey;
const value = [record[recordKey]];
const param = block.service.params?.[0] || {};
// 保留原有的 filter
const storedFilter = block.service.params?.[1]?.filters || {};
if (selectedRow.includes(record[ctx.rowKey])) {
if (selectedRow.includes(record[tableBlockContextBasicValue.rowKey])) {
if (block.dataLoadingMode === 'manual') {
return block.clearData();
}
@ -138,7 +148,7 @@ export const useTableBlockProps = () => {
storedFilter[uid] = {
$and: [
{
[target.field || ctx.rowKey]: {
[target.field || tableBlockContextBasicValue.rowKey]: {
[target.field ? '$in' : '$eq']: value,
},
},
@ -162,12 +172,16 @@ export const useTableBlockProps = () => {
});
// 更新表格的选中状态
setSelectedRow((prev) => (prev?.includes(record[ctx.rowKey]) ? [] : [record[ctx.rowKey]]));
setSelectedRow((prev) =>
prev?.includes(record[tableBlockContextBasicValue.rowKey])
? []
: [record[tableBlockContextBasicValue.rowKey]],
);
},
[ctx.rowKey, fieldSchema, getDataBlocks],
[tableBlockContextBasicValue.rowKey, fieldSchema, getDataBlocks],
),
onExpand: useCallback((expanded, record) => {
ctx?.field.onExpandClick?.(expanded, record);
tableBlockContextBasicValue.field.onExpandClick?.(expanded, record);
}, []),
};
};

View File

@ -433,47 +433,62 @@ const rowSelectCheckboxCheckedClassHover = css`
}
`;
const HeaderWrapperComponent = (props) => {
const HeaderWrapperComponent = React.memo((props) => {
return (
<DndContext>
<thead {...props} />
</DndContext>
);
};
});
const HeaderCellComponent = (props) => {
HeaderWrapperComponent.displayName = 'HeaderWrapperComponent';
const HeaderCellComponent = React.memo((props: { className: string }) => {
return <th {...props} className={cls(props.className, headerClass)} />;
};
});
const BodyRowComponent = (props: {
rowIndex: number;
onClick: (e: any) => void;
style: React.CSSProperties;
className: string;
}) => {
return <SortableRow {...props} />;
};
HeaderCellComponent.displayName = 'HeaderCellComponent';
const BodyCellComponent = (props) => {
const { token } = useToken();
const inView = useContext(InViewContext);
const isIndex = props.className?.includes('selection-column');
const { record, schema, rowIndex, isSubTable, ...others } = props;
const { valueMap } = useSatisfiedActionValues({ formValues: record, category: 'style', schema });
const style = useMemo(() => Object.assign({ ...props.style }, valueMap), [props.style, valueMap]);
const skeletonStyle = {
height: '1em',
backgroundColor: token.colorFillSecondary,
borderRadius: `${token.borderRadiusSM}px`,
};
const BodyRowComponent = React.memo(
(props: { rowIndex: number; onClick: (e: any) => void; style: React.CSSProperties; className: string }) => {
return <SortableRow {...props} />;
},
);
return (
<td {...others} className={classNames(props.className, cellClass)} style={style}>
{/* Lazy rendering cannot be used in sub-tables. */}
{isSubTable || inView || isIndex ? props.children : <div style={skeletonStyle} />}
</td>
);
};
BodyRowComponent.displayName = 'BodyRowComponent';
const BodyCellComponent = React.memo(
(props: {
className: string;
style: React.CSSProperties;
children: React.ReactNode;
record: any;
schema: any;
rowIndex: number;
isSubTable: boolean;
}) => {
const { token } = useToken();
const inView = useContext(InViewContext);
const isIndex = props.className?.includes('selection-column');
const { record, schema, rowIndex, isSubTable, ...others } = props;
const { valueMap } = useSatisfiedActionValues({ formValues: record, category: 'style', schema });
const style = useMemo(() => Object.assign({ ...props.style }, valueMap), [props.style, valueMap]);
const skeletonStyle = {
height: '1em',
backgroundColor: token.colorFillSecondary,
borderRadius: `${token.borderRadiusSM}px`,
};
return (
<td {...others} className={classNames(props.className, cellClass)} style={style}>
{/* Lazy rendering cannot be used in sub-tables. */}
{isSubTable || inView || isIndex ? props.children : <div style={skeletonStyle} />}
</td>
);
},
);
BodyCellComponent.displayName = 'BodyCellComponent';
interface TableProps {
/** @deprecated */