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

View File

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

View File

@ -433,28 +433,40 @@ const rowSelectCheckboxCheckedClassHover = css`
} }
`; `;
const HeaderWrapperComponent = (props) => { const HeaderWrapperComponent = React.memo((props) => {
return ( return (
<DndContext> <DndContext>
<thead {...props} /> <thead {...props} />
</DndContext> </DndContext>
); );
}; });
const HeaderCellComponent = (props) => { HeaderWrapperComponent.displayName = 'HeaderWrapperComponent';
const HeaderCellComponent = React.memo((props: { className: string }) => {
return <th {...props} className={cls(props.className, headerClass)} />; return <th {...props} className={cls(props.className, headerClass)} />;
}; });
const BodyRowComponent = (props: { HeaderCellComponent.displayName = 'HeaderCellComponent';
rowIndex: number;
onClick: (e: any) => void; const BodyRowComponent = React.memo(
style: React.CSSProperties; (props: { rowIndex: number; onClick: (e: any) => void; style: React.CSSProperties; className: string }) => {
className: string;
}) => {
return <SortableRow {...props} />; return <SortableRow {...props} />;
}; },
);
const BodyCellComponent = (props) => { 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 { token } = useToken();
const inView = useContext(InViewContext); const inView = useContext(InViewContext);
const isIndex = props.className?.includes('selection-column'); const isIndex = props.className?.includes('selection-column');
@ -473,7 +485,10 @@ const BodyCellComponent = (props) => {
{isSubTable || inView || isIndex ? props.children : <div style={skeletonStyle} />} {isSubTable || inView || isIndex ? props.children : <div style={skeletonStyle} />}
</td> </td>
); );
}; },
);
BodyCellComponent.displayName = 'BodyCellComponent';
interface TableProps { interface TableProps {
/** @deprecated */ /** @deprecated */