♻️ Refactor 'applyChanges' for better perf.
This commit is contained in:
@@ -54,100 +54,119 @@ function applyChanges(changes: any[], elements: any[]): any[] {
|
|||||||
return changes.filter((c) => c.type === 'reset').map((c) => c.item);
|
return changes.filter((c) => c.type === 'reset').map((c) => c.item);
|
||||||
}
|
}
|
||||||
|
|
||||||
let remainingChanges = [];
|
|
||||||
const updatedElements: any[] = [];
|
const updatedElements: any[] = [];
|
||||||
|
// By storing a map of changes for each element, we can a quick lookup as we
|
||||||
|
// iterate over the elements array!
|
||||||
|
const changesMap = new Map<any, any[]>();
|
||||||
|
|
||||||
for (const change of changes) {
|
for (const change of changes) {
|
||||||
if (change.type === 'add') {
|
if (change.type === 'add') {
|
||||||
updatedElements.push(change.item);
|
updatedElements.push(change.item);
|
||||||
|
continue;
|
||||||
|
} else if (change.type === 'remove') {
|
||||||
|
// For a 'remove' change we can safely ignore any other changes queued for
|
||||||
|
// the same element, it's going to be removed anyway!
|
||||||
|
changesMap.set(change.id, [change]);
|
||||||
} else {
|
} else {
|
||||||
remainingChanges.push(change);
|
const elementChanges = changesMap.get(change.id);
|
||||||
|
// If we already have a 'remove' change queued for this element, we don't
|
||||||
|
// need to bother queueing any other changes for it,
|
||||||
|
//
|
||||||
|
// We might end up needlessly iterating over this changes array but in
|
||||||
|
// practice it will be very small (e.g. less than 5 or so items).
|
||||||
|
if (elementChanges?.some((c) => c.type === 'remove')) continue;
|
||||||
|
if (elementChanges) {
|
||||||
|
// If we have some changes queued already, we can do a mutable update of
|
||||||
|
// that array and save ourselves some copying.
|
||||||
|
elementChanges.push(change);
|
||||||
|
} else {
|
||||||
|
changesMap.set(change.id, [change]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const item of elements) {
|
for (const element of elements) {
|
||||||
const nextChanges: any[] = [];
|
const changes = changesMap.get(element.id);
|
||||||
const _remainingChanges: any[] = [];
|
|
||||||
|
|
||||||
for (const change of remainingChanges) {
|
// When there are no changes for an element we can just push it unmodified,
|
||||||
if (change.id === item.id) {
|
// no need to copy it.
|
||||||
nextChanges.push(change);
|
if (!changes) {
|
||||||
} else {
|
updatedElements.push(element);
|
||||||
_remainingChanges.push(change);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
remainingChanges = _remainingChanges;
|
|
||||||
|
|
||||||
if (nextChanges.length === 0) {
|
|
||||||
updatedElements.push(item);
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateItem = { ...item };
|
// If we have a 'remove' change queued, it'll be the only change in the array
|
||||||
|
if (changes[0].type === 'remove') {
|
||||||
for (const currentChange of nextChanges) {
|
continue;
|
||||||
if (currentChange) {
|
|
||||||
switch (currentChange.type) {
|
|
||||||
case 'select': {
|
|
||||||
updateItem.selected = currentChange.selected;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'position': {
|
|
||||||
if (typeof currentChange.position !== 'undefined') {
|
|
||||||
updateItem.position = currentChange.position;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof currentChange.positionAbsolute !== 'undefined') {
|
|
||||||
if (!updateItem.computed) {
|
|
||||||
updateItem.computed = {};
|
|
||||||
}
|
|
||||||
updateItem.computed.positionAbsolute = currentChange.positionAbsolute;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof currentChange.dragging !== 'undefined') {
|
|
||||||
updateItem.dragging = currentChange.dragging;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (updateItem.expandParent) {
|
|
||||||
handleParentExpand(updatedElements, updateItem);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'dimensions': {
|
|
||||||
if (typeof currentChange.dimensions !== 'undefined') {
|
|
||||||
if (!updateItem.computed) {
|
|
||||||
updateItem.computed = {};
|
|
||||||
}
|
|
||||||
updateItem.computed.width = currentChange.dimensions.width;
|
|
||||||
updateItem.computed.height = currentChange.dimensions.height;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof currentChange.updateStyle !== 'undefined') {
|
|
||||||
updateItem.style = { ...(updateItem.style || {}), ...currentChange.dimensions };
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof currentChange.resizing === 'boolean') {
|
|
||||||
updateItem.resizing = currentChange.resizing;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (updateItem.expandParent) {
|
|
||||||
handleParentExpand(updatedElements, updateItem);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'remove': {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
updatedElements.push(updateItem);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// For other types of changes, we want to start with a shallow copy of the
|
||||||
|
// object so React knows this element has changed. Sequential changes will
|
||||||
|
/// each _mutate_ this object, so there's only ever one copy.
|
||||||
|
const updatedElement = { ...element };
|
||||||
|
|
||||||
|
for (const change of changes) {
|
||||||
|
applyChange(change, updatedElement, elements);
|
||||||
|
}
|
||||||
|
|
||||||
|
updatedElements.push(updatedElement);
|
||||||
}
|
}
|
||||||
|
|
||||||
return updatedElements;
|
return updatedElements;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Applies a single change to an element. This is a *mutable* update.
|
||||||
|
function applyChange(change: any, element: any, elements: any[] = []): any {
|
||||||
|
switch (change.type) {
|
||||||
|
case 'select': {
|
||||||
|
element.selected = change.selected;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'position': {
|
||||||
|
if (typeof change.position !== 'undefined') {
|
||||||
|
element.position = change.position;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof change.positionAbsolute !== 'undefined') {
|
||||||
|
element.computed ??= {};
|
||||||
|
element.computed.positionAbsolute = change.positionAbsolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof change.dragging !== 'undefined') {
|
||||||
|
element.dragging = change.dragging;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (element.expandParent) {
|
||||||
|
handleParentExpand(elements, element);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'dimensions': {
|
||||||
|
if (typeof change.dimensions !== 'undefined') {
|
||||||
|
element.computed ??= {};
|
||||||
|
element.computed.width = change.dimensions.width;
|
||||||
|
element.computed.height = change.dimensions.height;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof change.updateStyle !== 'undefined') {
|
||||||
|
element.style = Object.assign({}, element.style, change.updateStyle);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof change.resizing === 'boolean') {
|
||||||
|
element.resizing = change.resizing;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (element.expandParent) {
|
||||||
|
handleParentExpand(elements, element);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Drop in function that applies node changes to an array of nodes.
|
* Drop in function that applies node changes to an array of nodes.
|
||||||
* @public
|
* @public
|
||||||
|
|||||||
Reference in New Issue
Block a user