From 810642418087381077ff11ee8ca2a47bcf28664e Mon Sep 17 00:00:00 2001 From: Zeke Zhang <958414905@qq.com> Date: Sun, 3 Nov 2024 14:46:12 +0800 Subject: [PATCH] perf(useTableBlockProps): avoid unnecessary re-renders --- .../src/block-provider/TableBlockProvider.tsx | 11 ++- .../table/hooks/useTableBlockProps.tsx | 80 +++++++++++-------- .../schema-component/antd/table-v2/Table.tsx | 77 +++++++++++------- 3 files changed, 103 insertions(+), 65 deletions(-) diff --git a/packages/core/client/src/block-provider/TableBlockProvider.tsx b/packages/core/client/src/block-provider/TableBlockProvider.tsx index 7cd81ba510..c336e08208 100644 --- a/packages/core/client/src/block-provider/TableBlockProvider.tsx +++ b/packages/core/client/src/block-provider/TableBlockProvider.tsx @@ -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 diff --git a/packages/core/client/src/modules/blocks/data-blocks/table/hooks/useTableBlockProps.tsx b/packages/core/client/src/modules/blocks/data-blocks/table/hooks/useTableBlockProps.tsx index ccaf07d281..dba41e6cec 100644 --- a/packages/core/client/src/modules/blocks/data-blocks/table/hooks/useTableBlockProps.tsx +++ b/packages/core/client/src/modules/blocks/data-blocks/table/hooks/useTableBlockProps.tsx @@ -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(); 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); }, []), }; }; diff --git a/packages/core/client/src/schema-component/antd/table-v2/Table.tsx b/packages/core/client/src/schema-component/antd/table-v2/Table.tsx index 5105046878..c607623721 100644 --- a/packages/core/client/src/schema-component/antd/table-v2/Table.tsx +++ b/packages/core/client/src/schema-component/antd/table-v2/Table.tsx @@ -433,47 +433,62 @@ const rowSelectCheckboxCheckedClassHover = css` } `; -const HeaderWrapperComponent = (props) => { +const HeaderWrapperComponent = React.memo((props) => { return ( ); -}; +}); -const HeaderCellComponent = (props) => { +HeaderWrapperComponent.displayName = 'HeaderWrapperComponent'; + +const HeaderCellComponent = React.memo((props: { className: string }) => { return ; -}; +}); -const BodyRowComponent = (props: { - rowIndex: number; - onClick: (e: any) => void; - style: React.CSSProperties; - className: string; -}) => { - return ; -}; +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 ; + }, +); - return ( - - {/* Lazy rendering cannot be used in sub-tables. */} - {isSubTable || inView || isIndex ? props.children :
} - - ); -}; +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 ( + + {/* Lazy rendering cannot be used in sub-tables. */} + {isSubTable || inView || isIndex ? props.children :
} + + ); + }, +); + +BodyCellComponent.displayName = 'BodyCellComponent'; interface TableProps { /** @deprecated */