Skip to content

Commit

Permalink
[DataGrid] Performance: avoid style invalidation (#12019)
Browse files Browse the repository at this point in the history
  • Loading branch information
romgrk authored Feb 15, 2024
1 parent b797892 commit 4613e48
Show file tree
Hide file tree
Showing 14 changed files with 152 additions and 90 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -130,13 +130,12 @@ export const useGridColumnHeaders = (props: UseGridColumnHeadersProps) => {
return (
<GridColumnHeaderRow
ref={headerFiltersRef}
ownerState={{ params }}
className={classes.headerFilterRow}
role="row"
aria-rowindex={headerGroupingMaxDepth + 2}
ownerState={rootProps}
>
{filters}
{otherProps.getFiller(params, true)}
{otherProps.getFillers(params, filters, 0, true)}
</GridColumnHeaderRow>
);
};
Expand Down
6 changes: 2 additions & 4 deletions packages/x-data-grid-pro/src/tests/rows.DataGridPro.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
$,
$$,
grid,
gridOffsetTop,
getCell,
getRow,
getColumnValues,
Expand Down Expand Up @@ -445,7 +446,6 @@ describe('<DataGridPro /> - Rows', () => {
/>,
);

const root = grid('root')!;
const virtualScroller = grid('virtualScroller')!;
const renderingZone = grid('virtualScrollerRenderZone')!;
virtualScroller.scrollTop = 10e6; // scroll to the bottom
Expand All @@ -458,9 +458,7 @@ describe('<DataGridPro /> - Rows', () => {
); // Subtracting 1 is needed because of the column header borders
const scrollbarSize = apiRef.current.state.dimensions.scrollbarSize;
const distanceToFirstRow = (nbRows - renderingZone.children.length) * rowHeight;
const styles = getComputedStyle(root);
const offsetTop = parseInt(styles.getPropertyValue('--DataGrid-offsetTop'), 10);
expect(offsetTop).to.equal(distanceToFirstRow);
expect(gridOffsetTop()).to.equal(distanceToFirstRow);
expect(virtualScroller.scrollHeight - scrollbarSize).to.equal(nbRows * rowHeight);
});

Expand Down
15 changes: 13 additions & 2 deletions packages/x-data-grid/src/components/GridRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ import { findParentElementFromClassName, isEventTargetInPortal } from '../utils/
import { GRID_CHECKBOX_SELECTION_COL_DEF } from '../colDef/gridCheckboxSelectionColDef';
import { GRID_ACTIONS_COLUMN_TYPE } from '../colDef/gridActionsColDef';
import { GRID_DETAIL_PANEL_TOGGLE_FIELD } from '../constants/gridDetailPanelToggleField';
import { type GridDimensions } from '../hooks/features/dimensions';
import type { GridVirtualizationState } from '../hooks/features/virtualization';
import type { GridDimensions } from '../hooks/features/dimensions';
import { gridSortModelSelector } from '../hooks/features/sorting/gridSortingSelector';
import { gridRowMaximumTreeDepthSelector } from '../hooks/features/rows/gridRowsSelector';
import { gridColumnGroupsHeaderMaxDepthSelector } from '../hooks/features/columnGrouping/gridColumnGroupsSelector';
Expand All @@ -41,6 +42,7 @@ export interface GridRowProps extends React.HTMLAttributes<HTMLDivElement> {
*/
index: number;
rowHeight: number | 'auto';
offsets: GridVirtualizationState['offsets'];
dimensions: GridDimensions;
firstColumnToRender: number;
lastColumnToRender: number;
Expand Down Expand Up @@ -122,6 +124,7 @@ const GridRow = React.forwardRef<HTMLDivElement, GridRowProps>(function GridRow(
visibleColumns,
renderedColumns,
pinnedColumns,
offsets,
dimensions,
firstColumnToRender,
lastColumnToRender,
Expand Down Expand Up @@ -515,7 +518,11 @@ const GridRow = React.forwardRef<HTMLDivElement, GridRowProps>(function GridRow(
{...other}
>
{leftCells}
<div className={gridClasses.cellOffsetLeft} role="presentation" />
<div
role="presentation"
className={gridClasses.cellOffsetLeft}
style={{ width: offsets.left }}
/>
{cells}
{emptyCellWidth > 0 && <EmptyCell width={emptyCellWidth} />}
{rightCells.length > 0 && <div role="presentation" style={{ flex: '1' }} />}
Expand Down Expand Up @@ -581,6 +588,10 @@ GridRow.propTypes = {
isLastVisible: PropTypes.bool.isRequired,
isNotVisible: PropTypes.bool,
lastColumnToRender: PropTypes.number.isRequired,
offsets: PropTypes.shape({
left: PropTypes.number.isRequired,
top: PropTypes.number.isRequired,
}).isRequired,
onClick: PropTypes.func,
onDoubleClick: PropTypes.func,
onMouseEnter: PropTypes.func,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -189,8 +189,6 @@ export const GridRootStyles = styled('div', {
'--DataGrid-width': '0px',
'--DataGrid-hasScrollX': '0',
'--DataGrid-hasScrollY': '0',
'--DataGrid-offsetTop': '0px',
'--DataGrid-offsetLeft': '0px',
'--DataGrid-scrollbarSize': '10px',
'--DataGrid-rowWidth': '0px',
'--DataGrid-columnsTotalWidth': '0px',
Expand Down Expand Up @@ -575,7 +573,6 @@ export const GridRootStyles = styled('div', {
[`& .${c.cellOffsetLeft}`]: {
flex: '0 0 auto',
display: 'inline-block',
width: 'var(--DataGrid-offsetLeft)',
},
[`& .${c.columnHeaderDraggableContainer}`]: {
display: 'flex',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ import * as React from 'react';
import clsx from 'clsx';
import { styled, SxProps, Theme } from '@mui/system';
import { unstable_composeClasses as composeClasses } from '@mui/utils';
import { useGridApiContext } from '../../hooks/utils/useGridApiContext';
import { useGridSelector } from '../../hooks/utils/useGridSelector';
import { gridOffsetsSelector } from '../../hooks/features/virtualization';
import { useGridRootProps } from '../../hooks/utils/useGridRootProps';
import { getDataGridUtilityClass } from '../../constants/gridClasses';
import { DataGridProcessedProps } from '../../models/props/DataGridProps';
Expand All @@ -26,22 +29,26 @@ const VirtualScrollerRenderZoneRoot = styled('div', {
position: 'absolute',
display: 'flex', // Prevents margin collapsing when using `getRowSpacing`
flexDirection: 'column',
transform: 'translate3d(0, var(--DataGrid-offsetTop), 0)',
});

const GridVirtualScrollerRenderZone = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement> & { sx?: SxProps<Theme> }
>(function GridVirtualScrollerRenderZone(props, ref) {
const { className, ...other } = props;
const apiRef = useGridApiContext();
const rootProps = useGridRootProps();
const classes = useUtilityClasses(rootProps);
const offsets = useGridSelector(apiRef, gridOffsetsSelector);

return (
<VirtualScrollerRenderZoneRoot
ref={ref}
className={clsx(classes.root, className)}
ownerState={rootProps}
style={{
transform: `translate3d(0, ${offsets.top}px, 0)`,
}}
{...other}
/>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
import * as React from 'react';
import { unstable_useForkRef as useForkRef } from '@mui/utils';
import { styled } from '@mui/material/styles';
import { DataGridProcessedProps } from '../../../models/props/DataGridProps';
import { useGridSelector } from '../../utils';
import { useGridRootProps } from '../../utils/useGridRootProps';
import { useGridPrivateApiContext } from '../../utils/useGridPrivateApiContext';
import { GridRenderContext } from '../../../models/params/gridScrollParams';
import { useGridApiEventHandler } from '../../utils/useGridApiEventHandler';
import { GridEventListener } from '../../../models/events';
import { GridColumnHeaderItem } from '../../../components/columnHeaders/GridColumnHeaderItem';
import { gridDimensionsSelector } from '../dimensions';
import {
gridOffsetsSelector,
gridRenderContextColumnsSelector,
gridVirtualizationColumnEnabledSelector,
} from '../virtualization';
Expand Down Expand Up @@ -69,24 +72,16 @@ const SpaceFiller = styled('div')({
},
});

type OwnerState = DataGridProcessedProps;

export const GridColumnHeaderRow = styled('div', {
name: 'MuiDataGrid',
slot: 'ColumnHeaderRow',
overridesResolver: (props, styles) => styles.columnHeaderRow,
})<{ ownerState: { params?: GetHeadersParams; leftOverflow?: number } }>(
({ ownerState: { params: { position } = {}, leftOverflow = 0 } }) => ({
display: 'flex',
height: 'var(--DataGrid-headerHeight)',
transform:
position === undefined
? `translate3d(${
leftOverflow !== 0
? `calc(var(--DataGrid-offsetLeft) - ${leftOverflow}px)`
: 'var(--DataGrid-offsetLeft)'
}, 0, 0)`
: undefined,
}),
);
overridesResolver: (_, styles) => styles.columnHeaderRow,
})<{ ownerState: OwnerState }>({
display: 'flex',
height: 'var(--DataGrid-headerHeight)',
});

export const useGridColumnHeaders = (props: UseGridColumnHeadersProps) => {
const {
Expand All @@ -109,11 +104,13 @@ export const useGridColumnHeaders = (props: UseGridColumnHeadersProps) => {
const [resizeCol, setResizeCol] = React.useState('');

const apiRef = useGridPrivateApiContext();
const rootProps = useGridRootProps();
const hasVirtualization = useGridSelector(apiRef, gridVirtualizationColumnEnabledSelector);

const innerRef = React.useRef<HTMLDivElement>(null);
const handleInnerRef = useForkRef(innerRefProp, innerRef);
const dimensions = useGridSelector(apiRef, gridDimensionsSelector);
const offsets = useGridSelector(apiRef, gridOffsetsSelector);
const renderContext = useGridSelector(apiRef, gridRenderContextColumnsSelector);
const visiblePinnedColumns = useGridSelector(apiRef, gridVisiblePinnedColumnDefinitionsSelector);

Expand Down Expand Up @@ -163,15 +160,26 @@ export const useGridColumnHeaders = (props: UseGridColumnHeadersProps) => {
};
};

const getFiller = (params: GetHeadersParams | undefined, borderTop: boolean = false) => {
const getFillers = (
params: GetHeadersParams | undefined,
children: React.ReactNode,
leftOverflow: number,
borderTop: boolean = false,
) => {
const isPinnedRight = params?.position === GridPinnedColumnPosition.RIGHT;
const isNotPinned = params?.position === undefined;

const hasScrollbarFiller =
(visiblePinnedColumns.right.length > 0 && isPinnedRight) ||
(visiblePinnedColumns.right.length === 0 && params?.position === undefined);
(visiblePinnedColumns.right.length === 0 && isNotPinned);

const leftOffsetWidth = offsets.left - leftOverflow;

return (
<React.Fragment>
{params?.position === undefined && <SpaceFiller className={gridClasses.columnHeader} />}
{isNotPinned && <div role="presentation" style={{ width: leftOffsetWidth }} />}
{children}
{isNotPinned && <SpaceFiller className={gridClasses.columnHeader} />}
{hasScrollbarFiller && (
<ScrollbarFiller header borderTop={borderTop} pinnedRight={isPinnedRight} />
)}
Expand Down Expand Up @@ -220,10 +228,9 @@ export const useGridColumnHeaders = (props: UseGridColumnHeadersProps) => {
<GridColumnHeaderRow
role="row"
aria-rowindex={headerGroupingMaxDepth + 1}
ownerState={{ params }}
ownerState={rootProps}
>
{columns}
{getFiller(params)}
{getFillers(params, columns, 0)}
</GridColumnHeaderRow>
);
};
Expand Down Expand Up @@ -323,36 +330,34 @@ export const useGridColumnHeaders = (props: UseGridColumnHeadersProps) => {
}

headerToRender.forEach((depthInfo, depthIndex) => {
const children = depthInfo.elements.map(
({ groupId, width, fields, colIndex, hasFocus, tabIndex }, groupIndex) => {
return (
<GridColumnGroupHeader
key={groupIndex}
groupId={groupId}
width={width}
fields={fields}
colIndex={colIndex}
depth={depthIndex}
isLastColumn={colIndex === visibleColumns.length - fields.length}
maxDepth={headerToRender.length}
height={dimensions.headerHeight}
hasFocus={hasFocus}
tabIndex={tabIndex}
/>
);
},
);

columns.push(
<GridColumnHeaderRow
key={depthIndex}
role="row"
aria-rowindex={depthIndex + 1}
ownerState={{
params,
leftOverflow: depthInfo.leftOverflow,
}}
ownerState={rootProps}
>
{depthInfo.elements.map(
({ groupId, width, fields, colIndex, hasFocus, tabIndex }, groupIndex) => {
return (
<GridColumnGroupHeader
key={groupIndex}
groupId={groupId}
width={width}
fields={fields}
colIndex={colIndex}
depth={depthIndex}
isLastColumn={colIndex === visibleColumns.length - fields.length}
maxDepth={headerToRender.length}
height={dimensions.headerHeight}
hasFocus={hasFocus}
tabIndex={tabIndex}
/>
);
},
)}
{getFiller(params)}
{getFillers(params, children, depthInfo.leftOverflow)}
</GridColumnHeaderRow>,
);
});
Expand All @@ -361,7 +366,7 @@ export const useGridColumnHeaders = (props: UseGridColumnHeadersProps) => {

return {
renderContext,
getFiller,
getFillers,
getColumnHeaders,
getColumnsToRender,
getColumnGroupHeaders,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,16 @@ export const gridRenderContextSelector = createSelector(
(state) => state.renderContext,
);

/**
* Get the offsets
* @category Virtualization
* @ignore - do not document.
*/
export const gridOffsetsSelector = createSelector(
gridVirtualizationSelector,
(state) => state.offsets,
);

/**
* Get the render context, with only columns filled in.
* This is cached, so it can be used to only re-render when the column interval changes.
Expand Down
Loading

0 comments on commit 4613e48

Please sign in to comment.