Skip to content

Commit

Permalink
perf(react-grid): update only changes rows in table (#199)
Browse files Browse the repository at this point in the history
  • Loading branch information
kvet authored Jul 17, 2017
1 parent af26c64 commit e1c64ff
Show file tree
Hide file tree
Showing 9 changed files with 207 additions and 133 deletions.
4 changes: 3 additions & 1 deletion packages/dx-react-core/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ export { Getter } from './plugged/getter';
export { Watcher } from './plugged/watcher';
export { Template } from './plugged/template';
export { TemplatePlaceholder } from './plugged/template-placeholder';
export { combineTemplates } from './utils/templateHelpers';

export { DragDropContext } from './drag-drop/context';
export { DragSource } from './drag-drop/source';
export { DropTarget } from './drag-drop/target';

export { TemplateRenderer } from './template-component';
export { combineTemplates } from './utils/templateHelpers';
2 changes: 2 additions & 0 deletions packages/dx-react-core/src/template-component.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export const TemplateRenderer = ({ template, children, ...restProps }) =>
template({ ...restProps, children });
20 changes: 20 additions & 0 deletions packages/dx-react-core/src/template-component.test.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import React from 'react';
import { mount } from 'enzyme';

import { TemplateRenderer } from './template-component';

describe('TemplateRenderer', () => {
it('should work', () => {
const tree = mount(
<TemplateRenderer
template={({ test, children }) => <div className={test}>{children}</div>}
test={'test'}
>
<div className="content" />
</TemplateRenderer>,
);

expect(tree.find('.test > .content').exists())
.toBe(true);
});
});
3 changes: 2 additions & 1 deletion packages/dx-react-grid-bootstrap3/src/templates/table.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@ import {
const MINIMAL_COLUMN_WIDTH = 120;

/* eslint-disable react/prop-types */
const tableTemplate = ({ children, ...restProps }) => (
const tableTemplate = ({ children, tableRef, ...restProps }) => (
<table
className="table"
ref={tableRef}
{...restProps}
>
{children}
Expand Down
4 changes: 2 additions & 2 deletions packages/dx-react-grid-material-ui/src/templates/table.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ import {
const MINIMAL_COLUMN_WIDTH = 120;

/* eslint-disable react/prop-types */
const tableTemplate = ({ children, ...restProps }) => (
<TableMUI {...restProps}>{children}</TableMUI>
const tableTemplate = ({ children, tableRef, ...restProps }) => (
<TableMUI ref={tableRef} {...restProps}>{children}</TableMUI>
);
const headTemplate = ({ children, ...restProps }) => (
<TableHeadMUI {...restProps}>{children}</TableHeadMUI>
Expand Down
150 changes: 43 additions & 107 deletions packages/dx-react-grid/src/components/table-layout.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,90 +6,21 @@ import { findDOMNode } from 'react-dom';

import {
DropTarget,
TemplateRenderer,
} from '@devexpress/dx-react-core';

import {
tableRowKeyGetter,
tableColumnKeyGetter,
getTableCellInfo,
findTableCellTarget,
getTableColumnGeometries,
getTableTargetColumnIndex,
getAnimations,
filterActiveAnimations,
evalAnimations,
} from '@devexpress/dx-grid-core';

const FLEX_TYPE = 'flex';

const getColumnStyle = ({ column, animationState = {} }) => ({
width: column.width !== undefined ? `${column.width}px` : undefined,
...animationState,
});

const getRowStyle = ({ row }) => ({
height: row.height !== undefined ? `${row.height}px` : undefined,
});

const renderRowCells = ({ row, columns, cellTemplate, animationState }) =>
columns
.filter((column, columnIndex) => !getTableCellInfo({ row, columns, columnIndex }).skip)
.map((column, columnIndex) => {
const key = tableColumnKeyGetter(column, columnIndex);
const colspan = getTableCellInfo({ row, columns, columnIndex }).colspan;
return React.cloneElement(
cellTemplate({
row,
column,
...colspan ? { colspan } : null,
style: getColumnStyle({ column, animationState: animationState.get(key) }),
}),
{ key },
);
});

const renderRows = ({
rows,
getRowId,
columns,
rowTemplate,
cellTemplate,
animationState,
}) =>
rows
.map((row, rowIndex) => React.cloneElement(
rowTemplate({
row,
style: getRowStyle({ row }),
children: renderRowCells({ row, columns, cellTemplate, animationState }),
}),
{ key: tableRowKeyGetter(getRowId, row, rowIndex) },
));
import { RowsBlockLayout } from './table-layout/rows-block-layout';

const renderRowsBlock = ({
rows,
getRowId,
columns,
blockTemplate,
rowTemplate,
cellTemplate,
onClick,
animationState,
}) => blockTemplate({
onClick: (e) => {
const { rowIndex, columnIndex } = findTableCellTarget(e);
if (rowIndex === -1 || columnIndex === -1) return;
onClick({ e, row: rows[rowIndex], column: columns[columnIndex] });
},
children: renderRows({
rows,
getRowId,
columns,
rowTemplate,
cellTemplate,
animationState,
}),
});
const FLEX_TYPE = 'flex';

export class TableLayout extends React.PureComponent {
constructor(props) {
Expand Down Expand Up @@ -229,41 +160,46 @@ export class TableLayout extends React.PureComponent {
.map(column => column.width || (column.type === FLEX_TYPE ? 0 : minColumnWidth))
.reduce((accum, width) => accum + width, 0);

const table = tableTemplate({
style: {
tableLayout: 'fixed',
minWidth: `${minWidth}px`,
},
ref: (node) => { if (node) this.tableNode = node; },
children: [
!headerRows.length ? null : React.cloneElement(
renderRowsBlock({
rows: headerRows,
getRowId,
columns,
blockTemplate: headTemplate,
rowTemplate,
cellTemplate,
onClick,
animationState,
}),
{ key: 'head' },
),
React.cloneElement(
renderRowsBlock({
rows,
getRowId,
columns,
blockTemplate: bodyTemplate,
rowTemplate,
cellTemplate,
onClick,
animationState,
}),
{ key: 'body' },
),
],
});
const table = (
<TemplateRenderer
template={tableTemplate}
style={{
tableLayout: 'fixed',
minWidth: `${minWidth}px`,
}}
tableRef={(node) => { if (node) this.tableNode = node; }}
>
{[
...(!headerRows.length
? []
: [(
<RowsBlockLayout
key="head"
rows={headerRows}
getRowId={getRowId}
columns={columns}
blockTemplate={headTemplate}
rowTemplate={rowTemplate}
cellTemplate={cellTemplate}
onClick={onClick}
animationState={animationState}
/>
)]
),
<RowsBlockLayout
key="body"
rows={rows}
getRowId={getRowId}
columns={columns}
blockTemplate={bodyTemplate}
rowTemplate={rowTemplate}
cellTemplate={cellTemplate}
onClick={onClick}
animationState={animationState}
/>,
]}
</TemplateRenderer>
);

return (
<div
Expand Down
26 changes: 4 additions & 22 deletions packages/dx-react-grid/src/components/table-layout.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { TableLayout } from './table-layout';
const PropsContainer = () => null;
const tableTemplateMock = ({ children, ...props }) => (
<table
ref={props.ref}
ref={props.tableRef}
>
<PropsContainer {...props} />
{children}
Expand Down Expand Up @@ -92,7 +92,7 @@ describe('TableLayout', () => {
/>,
);

testTablePart({ tree: tree.find('table > tbody'), rows, columns });
testTablePart({ tree: tree.find('table tbody'), rows, columns });
});

it('should render table with headerRows and columns', () => {
Expand All @@ -112,7 +112,7 @@ describe('TableLayout', () => {
/>,
);

testTablePart({ tree: tree.find('table > thead'), rows, columns });
testTablePart({ tree: tree.find('table thead'), rows, columns });
});

it('should span columns if specified', () => {
Expand All @@ -138,7 +138,7 @@ describe('TableLayout', () => {

rowColumn = rowWrappers.at(1).find('td');
expect(rowColumn.length).toBe(2);
expect(rowColumn.at(0).children(PropsContainer).props().colspan).toBe(undefined);
expect(rowColumn.at(0).children(PropsContainer).props()).not.toHaveProperty('colspan');
expect(rowColumn.at(1).children(PropsContainer).props().colspan).toBe(3);
});

Expand Down Expand Up @@ -229,24 +229,6 @@ describe('TableLayout', () => {
.toMatchObject({ row: rows[1], column: columns[1], e: {} });
});

it('should not pass the "colspan" parameter to the cellTemplate if it is undefined', () => {
const cellTemplate = jest.fn().mockImplementation(cellTemplateMock);
mount(
<TableLayout
rows={[{ id: 1 }]}
columns={[{ name: 'a' }]}
tableTemplate={tableTemplateMock}
bodyTemplate={bodyTemplateMock}
rowTemplate={rowTemplateMock}
cellTemplate={cellTemplate}
getRowId={row => row.id}
/>,
);

expect(cellTemplate.mock.calls[0][0])
.not.toHaveProperty('colspan');
});

describe('flex column', () => {
it('should add flex column if all columns have fixed widths', () => {
const rows = [{ id: 1 }, { id: 2 }];
Expand Down
67 changes: 67 additions & 0 deletions packages/dx-react-grid/src/components/table-layout/row-layout.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import React from 'react';
import PropTypes from 'prop-types';

import {
TemplateRenderer,
} from '@devexpress/dx-react-core';

import {
tableColumnKeyGetter,
getTableCellInfo,
} from '@devexpress/dx-grid-core';

const getColumnStyle = ({ column, animationState = {} }) => ({
width: column.width !== undefined ? `${column.width}px` : undefined,
...animationState,
});

const getRowStyle = ({ row }) => ({
height: row.height !== undefined ? `${row.height}px` : undefined,
});

export class RowLayout extends React.PureComponent {
render() {
const {
row,
columns,
rowTemplate,
cellTemplate,
animationState,
} = this.props;

return (
<TemplateRenderer
template={rowTemplate}
row={row}
style={getRowStyle({ row })}
>
{
columns
.filter((column, columnIndex) => !getTableCellInfo({ row, columns, columnIndex }).skip)
.map((column, columnIndex) => {
const key = tableColumnKeyGetter(column, columnIndex);
const colspan = getTableCellInfo({ row, columns, columnIndex }).colspan;
return (
<TemplateRenderer
key={key}
template={cellTemplate}
row={row}
column={column}
style={getColumnStyle({ column, animationState: animationState.get(key) })}
{...colspan ? { colspan } : null}
/>
);
})
}
</TemplateRenderer>
);
}
}

RowLayout.propTypes = {
row: PropTypes.object.isRequired,
columns: PropTypes.array.isRequired,
rowTemplate: PropTypes.func.isRequired,
cellTemplate: PropTypes.func.isRequired,
animationState: PropTypes.instanceOf(Map).isRequired,
};
Loading

0 comments on commit e1c64ff

Please sign in to comment.