Skip to content

Commit 1078b14

Browse files
STRMLzaykaalexander
andauthoredFeb 25, 2020
Add shouldComponentUpdate to major components. (react-grid-layout#1123)
* Fix freezing on drag ie11 react-grid-layout#760 1) Add shouldComponentUpdate for ReactGridLayout 2) Add shouldComponentUpdate for GridItem 3) Fix when items are dragging not from own position, but from zero top left * feat(render): Add shouldComponentUpdate to ReactGridLayout & GridItem. This improves performance quite a bit in most cases, is implemented safely to avoid breakage on equal arrays and objects. To do so, while avoiding the maintenance burden of keeping a keylist in sync with propTypes, we read propTypes using babel-plugin-preval then construct a function to do the fastest possible comparison. This is *a lot* faster than isEqual while avoiding unnecessary comparison of e.g. `children`. * fix(test): complete refactor of calculateUtils Co-authored-by: Alexander Zayka <[email protected]>
1 parent 663c06f commit 1078b14

11 files changed

+782
-485
lines changed
 

‎.babelrc.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ module.exports = {
1313
],
1414
plugins: [
1515
"@babel/plugin-transform-flow-comments",
16-
"@babel/plugin-proposal-class-properties"
16+
"@babel/plugin-proposal-class-properties",
17+
"babel-plugin-preval"
1718
]
1819
};

‎lib/GridItem.jsx

+46-13
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ import ReactDOM from "react-dom";
44
import PropTypes from "prop-types";
55
import { DraggableCore } from "react-draggable";
66
import { Resizable } from "react-resizable";
7-
import { perc, setTopLeft, setTransform } from "./utils";
8-
import { calcPosition, calcXY, calcWH } from "./calculateUtils";
7+
import { fastPositionEqual, perc, setTopLeft, setTransform } from "./utils";
8+
import { calcGridItemPosition, calcXY, calcWH } from "./calculateUtils";
99
import classNames from "classnames";
1010
import type { Element as ReactElement, Node as ReactNode } from "react";
1111

@@ -178,6 +178,31 @@ export default class GridItem extends React.Component<Props, State> {
178178

179179
currentNode: HTMLElement;
180180

181+
shouldComponentUpdate(nextProps: Props, nextState: State) {
182+
let { x, y, w, h } = this.props;
183+
const oldPosition = calcGridItemPosition(
184+
this.getPositionParams(this.props),
185+
x,
186+
y,
187+
w,
188+
h,
189+
this.state
190+
);
191+
({ x, y, w, h } = nextProps);
192+
const newPosition = calcGridItemPosition(
193+
this.getPositionParams(nextProps),
194+
x,
195+
y,
196+
w,
197+
h,
198+
nextState
199+
);
200+
return (
201+
!fastPositionEqual(oldPosition, newPosition) ||
202+
this.props.useCSSTransforms !== nextProps.useCSSTransforms
203+
);
204+
}
205+
181206
componentDidUpdate(prevProps: Props) {
182207
this.moveDroppingItem(prevProps);
183208
}
@@ -220,14 +245,14 @@ export default class GridItem extends React.Component<Props, State> {
220245
}
221246
}
222247

223-
getPositionParams(): PositionParams {
248+
getPositionParams(props: Props = this.props): PositionParams {
224249
return {
225-
cols: this.props.cols,
226-
containerPadding: this.props.containerPadding,
227-
containerWidth: this.props.containerWidth,
228-
margin: this.props.margin,
229-
maxRows: this.props.maxRows,
230-
rowHeight: this.props.rowHeight
250+
cols: props.cols,
251+
containerPadding: props.containerPadding,
252+
containerWidth: props.containerWidth,
253+
margin: props.margin,
254+
maxRows: props.maxRows,
255+
rowHeight: props.rowHeight
231256
};
232257
}
233258

@@ -304,11 +329,12 @@ export default class GridItem extends React.Component<Props, State> {
304329
const positionParams = this.getPositionParams();
305330

306331
// This is the max possible width - doesn't go to infinity because of the width of the window
307-
const maxWidth = calcPosition(positionParams, 0, 0, cols - x, 0).width;
332+
const maxWidth = calcGridItemPosition(positionParams, 0, 0, cols - x, 0)
333+
.width;
308334

309335
// Calculate min/max constraints using our min & maxes
310-
const mins = calcPosition(positionParams, 0, 0, minW, minH);
311-
const maxes = calcPosition(positionParams, 0, 0, maxW, maxH);
336+
const mins = calcGridItemPosition(positionParams, 0, 0, minW, minH);
337+
const maxes = calcGridItemPosition(positionParams, 0, 0, maxW, maxH);
312338
const minConstraints = [mins.width, mins.height];
313339
const maxConstraints = [
314340
Math.min(maxes.width, maxWidth),
@@ -534,7 +560,14 @@ export default class GridItem extends React.Component<Props, State> {
534560
useCSSTransforms
535561
} = this.props;
536562

537-
const pos = calcPosition(this.getPositionParams(), x, y, w, h, this.state);
563+
const pos = calcGridItemPosition(
564+
this.getPositionParams(),
565+
x,
566+
y,
567+
w,
568+
h,
569+
this.state
570+
);
538571
const child = React.Children.only(this.props.children);
539572

540573
// Create the child element. We clone the existing element but modify its className and style.

‎lib/ReactGridLayout.jsx

+16-179
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// @flow
22
import React from "react";
3-
import PropTypes from "prop-types";
3+
44
import isEqual from "lodash.isequal";
55
import classNames from "classnames";
66
import {
@@ -12,22 +12,23 @@ import {
1212
getLayoutItem,
1313
moveElement,
1414
synchronizeLayoutWithChildren,
15-
validateLayout,
1615
getAllCollisions,
17-
noop
16+
compactType,
17+
noop,
18+
fastRGLPropsEqual
1819
} from "./utils";
1920

2021
import { calcXY } from "./calculateUtils";
2122

2223
import GridItem from "./GridItem";
24+
import ReactGridLayoutPropTypes from "./ReactGridLayoutPropTypes";
2325
import type {
2426
ChildrenArray as ReactChildrenArray,
2527
Element as ReactElement
2628
} from "react";
2729

2830
// Types
2931
import type {
30-
EventCallback,
3132
CompactType,
3233
GridResizeEvent,
3334
GridDragEvent,
@@ -54,53 +55,9 @@ type State = {
5455
propsLayout?: Layout
5556
};
5657

57-
export type Props = {
58-
className: string,
59-
style: Object,
60-
width: number,
61-
autoSize: boolean,
62-
cols: number,
63-
draggableCancel: string,
64-
draggableHandle: string,
65-
verticalCompact: boolean,
66-
compactType: CompactType,
67-
layout: Layout,
68-
margin: [number, number],
69-
containerPadding: [number, number] | null,
70-
rowHeight: number,
71-
maxRows: number,
72-
isDraggable: boolean,
73-
isResizable: boolean,
74-
isDroppable: boolean,
75-
preventCollision: boolean,
76-
useCSSTransforms: boolean,
77-
transformScale: number,
78-
droppingItem: $Shape<LayoutItem>,
79-
80-
// Callbacks
81-
onLayoutChange: Layout => void,
82-
onDrag: EventCallback,
83-
onDragStart: EventCallback,
84-
onDragStop: EventCallback,
85-
onResize: EventCallback,
86-
onResizeStart: EventCallback,
87-
onResizeStop: EventCallback,
88-
onDrop: (itemPosition: {
89-
x: number,
90-
y: number,
91-
w: number,
92-
h: number,
93-
e: Event
94-
}) => void,
95-
children: ReactChildrenArray<ReactElement<any>>
96-
};
97-
// End Types
98-
99-
const compactType = (props: Props): CompactType => {
100-
const { verticalCompact, compactType } = props || {};
58+
import type { Props } from "./ReactGridLayoutPropTypes";
10159

102-
return verticalCompact === false ? null : compactType;
103-
};
60+
// End Types
10461

10562
const layoutClassName = "react-grid-layout";
10663
let isFirefox = false;
@@ -119,135 +76,8 @@ export default class ReactGridLayout extends React.Component<Props, State> {
11976
// TODO publish internal ReactClass displayName transform
12077
static displayName = "ReactGridLayout";
12178

122-
static propTypes = {
123-
//
124-
// Basic props
125-
//
126-
className: PropTypes.string,
127-
style: PropTypes.object,
128-
129-
// This can be set explicitly. If it is not set, it will automatically
130-
// be set to the container width. Note that resizes will *not* cause this to adjust.
131-
// If you need that behavior, use WidthProvider.
132-
width: PropTypes.number,
133-
134-
// If true, the container height swells and contracts to fit contents
135-
autoSize: PropTypes.bool,
136-
// # of cols.
137-
cols: PropTypes.number,
138-
139-
// A selector that will not be draggable.
140-
draggableCancel: PropTypes.string,
141-
// A selector for the draggable handler
142-
draggableHandle: PropTypes.string,
143-
144-
// Deprecated
145-
verticalCompact: function(props: Props) {
146-
if (
147-
props.verticalCompact === false &&
148-
process.env.NODE_ENV !== "production"
149-
) {
150-
console.warn(
151-
// eslint-disable-line no-console
152-
"`verticalCompact` on <ReactGridLayout> is deprecated and will be removed soon. " +
153-
'Use `compactType`: "horizontal" | "vertical" | null.'
154-
);
155-
}
156-
},
157-
// Choose vertical or hotizontal compaction
158-
compactType: PropTypes.oneOf(["vertical", "horizontal"]),
159-
160-
// layout is an array of object with the format:
161-
// {x: Number, y: Number, w: Number, h: Number, i: String}
162-
layout: function(props: Props) {
163-
var layout = props.layout;
164-
// I hope you're setting the data-grid property on the grid items
165-
if (layout === undefined) return;
166-
validateLayout(layout, "layout");
167-
},
168-
169-
//
170-
// Grid Dimensions
171-
//
172-
173-
// Margin between items [x, y] in px
174-
margin: PropTypes.arrayOf(PropTypes.number),
175-
// Padding inside the container [x, y] in px
176-
containerPadding: PropTypes.arrayOf(PropTypes.number),
177-
// Rows have a static height, but you can change this based on breakpoints if you like
178-
rowHeight: PropTypes.number,
179-
// Default Infinity, but you can specify a max here if you like.
180-
// Note that this isn't fully fleshed out and won't error if you specify a layout that
181-
// extends beyond the row capacity. It will, however, not allow users to drag/resize
182-
// an item past the barrier. They can push items beyond the barrier, though.
183-
// Intentionally not documented for this reason.
184-
maxRows: PropTypes.number,
185-
186-
//
187-
// Flags
188-
//
189-
isDraggable: PropTypes.bool,
190-
isResizable: PropTypes.bool,
191-
// If true, grid items won't change position when being dragged over.
192-
preventCollision: PropTypes.bool,
193-
// Use CSS transforms instead of top/left
194-
useCSSTransforms: PropTypes.bool,
195-
// parent layout transform scale
196-
transformScale: PropTypes.number,
197-
// If true, an external element can trigger onDrop callback with a specific grid position as a parameter
198-
isDroppable: PropTypes.bool,
199-
200-
//
201-
// Callbacks
202-
//
203-
204-
// Callback so you can save the layout. Calls after each drag & resize stops.
205-
onLayoutChange: PropTypes.func,
206-
207-
// Calls when drag starts. Callback is of the signature (layout, oldItem, newItem, placeholder, e, ?node).
208-
// All callbacks below have the same signature. 'start' and 'stop' callbacks omit the 'placeholder'.
209-
onDragStart: PropTypes.func,
210-
// Calls on each drag movement.
211-
onDrag: PropTypes.func,
212-
// Calls when drag is complete.
213-
onDragStop: PropTypes.func,
214-
//Calls when resize starts.
215-
onResizeStart: PropTypes.func,
216-
// Calls when resize movement happens.
217-
onResize: PropTypes.func,
218-
// Calls when resize is complete.
219-
onResizeStop: PropTypes.func,
220-
// Calls when some element is dropped.
221-
onDrop: PropTypes.func,
222-
223-
//
224-
// Other validations
225-
//
226-
227-
droppingItem: PropTypes.shape({
228-
i: PropTypes.string.isRequired,
229-
w: PropTypes.number.isRequired,
230-
h: PropTypes.number.isRequired
231-
}),
232-
233-
// Children must not have duplicate keys.
234-
children: function(props: Props, propName: string) {
235-
var children = props[propName];
236-
237-
// Check children keys for duplicates. Throw if found.
238-
var keys = {};
239-
React.Children.forEach(children, function(child) {
240-
if (keys[child.key]) {
241-
throw new Error(
242-
'Duplicate child key "' +
243-
child.key +
244-
'" found! This will cause problems in ReactGridLayout.'
245-
);
246-
}
247-
keys[child.key] = true;
248-
});
249-
}
250-
};
79+
// Refactored to another module to make way for preval
80+
static propTypes = ReactGridLayoutPropTypes;
25181

25282
static defaultProps = {
25383
autoSize: true,
@@ -365,6 +195,13 @@ export default class ReactGridLayout extends React.Component<Props, State> {
365195
return null;
366196
}
367197

198+
shouldComponentUpdate(nextProps: Props, nextState: State) {
199+
return (
200+
!fastRGLPropsEqual(this.props, nextProps, isEqual) ||
201+
!isEqual(this.state.activeDrag, nextState.activeDrag)
202+
);
203+
}
204+
368205
componentDidUpdate(prevProps: Props, prevState: State) {
369206
if (!this.state.activeDrag) {
370207
const newLayout = this.state.layout;

0 commit comments

Comments
 (0)
Please sign in to comment.