mirror of
https://github.com/nocobase/nocobase
synced 2024-11-15 23:46:02 +00:00
perf(useTableBlockProps): avoid unnecessary re-renders
This commit is contained in:
parent
bca6d965ee
commit
8106424180
@ -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
|
||||
|
@ -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);
|
||||
}, []),
|
||||
};
|
||||
};
|
||||
|
@ -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 */
|
||||
|
Loading…
Reference in New Issue
Block a user