[Refactoring] Teact: Add comments to teactFastList algorithm
This commit is contained in:
parent
247d8a354b
commit
d3e3cced6c
@ -291,6 +291,8 @@ function renderChildren(
|
|||||||
return newChildren;
|
return newChildren;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This function allows to prepend/append a bunch of new DOM nodes to the top/bottom of preserved ones.
|
||||||
|
// It also allows to selectively move particular preserved nodes within their DOM list.
|
||||||
function renderFastListChildren($current: VirtualRealElement, $new: VirtualRealElement, currentEl: HTMLElement) {
|
function renderFastListChildren($current: VirtualRealElement, $new: VirtualRealElement, currentEl: HTMLElement) {
|
||||||
const newKeys = new Set(
|
const newKeys = new Set(
|
||||||
$new.children.map(($newChild) => {
|
$new.children.map(($newChild) => {
|
||||||
@ -306,6 +308,7 @@ function renderFastListChildren($current: VirtualRealElement, $new: VirtualRealE
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Build a collection of old children that also remain in the new list
|
||||||
let currentRemainingIndex = 0;
|
let currentRemainingIndex = 0;
|
||||||
const remainingByKey = $current.children
|
const remainingByKey = $current.children
|
||||||
.reduce((acc, $currentChild, i) => {
|
.reduce((acc, $currentChild, i) => {
|
||||||
@ -313,7 +316,7 @@ function renderFastListChildren($current: VirtualRealElement, $new: VirtualRealE
|
|||||||
// eslint-disable-next-line no-null/no-null
|
// eslint-disable-next-line no-null/no-null
|
||||||
const isKeyPresent = key !== undefined && key !== null;
|
const isKeyPresent = key !== undefined && key !== null;
|
||||||
|
|
||||||
// First we handle removed children
|
// First we process removed children
|
||||||
if (isKeyPresent && !newKeys.has(key)) {
|
if (isKeyPresent && !newKeys.has(key)) {
|
||||||
renderWithVirtual(currentEl, $currentChild, undefined, $new, -1);
|
renderWithVirtual(currentEl, $currentChild, undefined, $new, -1);
|
||||||
|
|
||||||
@ -324,6 +327,7 @@ function renderFastListChildren($current: VirtualRealElement, $new: VirtualRealE
|
|||||||
// If a non-key element remains at the same index we preserve it with a virtual `key`
|
// If a non-key element remains at the same index we preserve it with a virtual `key`
|
||||||
if ($newChild && !newChildKey) {
|
if ($newChild && !newChildKey) {
|
||||||
key = `${INDEX_KEY_PREFIX}${i}`;
|
key = `${INDEX_KEY_PREFIX}${i}`;
|
||||||
|
// Otherwise, we just remove it
|
||||||
} else {
|
} else {
|
||||||
renderWithVirtual(currentEl, $currentChild, undefined, $new, -1);
|
renderWithVirtual(currentEl, $currentChild, undefined, $new, -1);
|
||||||
|
|
||||||
@ -335,14 +339,14 @@ function renderFastListChildren($current: VirtualRealElement, $new: VirtualRealE
|
|||||||
acc[key] = {
|
acc[key] = {
|
||||||
$element: $currentChild,
|
$element: $currentChild,
|
||||||
index: currentRemainingIndex++,
|
index: currentRemainingIndex++,
|
||||||
order: 'props' in $currentChild ? $currentChild.props.teactOrderKey : undefined,
|
orderKey: 'props' in $currentChild ? $currentChild.props.teactOrderKey : undefined,
|
||||||
};
|
};
|
||||||
return acc;
|
return acc;
|
||||||
}, {} as Record<string, { $element: VirtualElement; index: number; order?: number }>);
|
}, {} as Record<string, { $element: VirtualElement; index: number; orderKey?: number }>);
|
||||||
|
|
||||||
let newChildren: VirtualElement[] = [];
|
let newChildren: VirtualElement[] = [];
|
||||||
|
|
||||||
let fragmentQueue: VirtualElement[] | undefined;
|
let fragmentElements: VirtualElement[] | undefined;
|
||||||
let fragmentIndex: number | undefined;
|
let fragmentIndex: number | undefined;
|
||||||
|
|
||||||
let currentPreservedIndex = 0;
|
let currentPreservedIndex = 0;
|
||||||
@ -352,28 +356,30 @@ function renderFastListChildren($current: VirtualRealElement, $new: VirtualRealE
|
|||||||
const currentChildInfo = remainingByKey[key];
|
const currentChildInfo = remainingByKey[key];
|
||||||
|
|
||||||
if (!currentChildInfo) {
|
if (!currentChildInfo) {
|
||||||
// All new nodes are queued to be inserted with fragments if possible.
|
if (!fragmentElements) {
|
||||||
if (!fragmentQueue) {
|
fragmentElements = [];
|
||||||
fragmentQueue = [];
|
|
||||||
fragmentIndex = i;
|
fragmentIndex = i;
|
||||||
}
|
}
|
||||||
|
|
||||||
fragmentQueue.push($newChild);
|
fragmentElements.push($newChild);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fragmentQueue) {
|
// This prepends new children to the top
|
||||||
newChildren = newChildren.concat(flushFragmentQueue(fragmentQueue, fragmentIndex!, currentEl, $new));
|
if (fragmentElements) {
|
||||||
|
newChildren = newChildren.concat(renderFragment(fragmentElements, fragmentIndex!, currentEl, $new));
|
||||||
|
fragmentElements = undefined;
|
||||||
fragmentIndex = undefined;
|
fragmentIndex = undefined;
|
||||||
fragmentQueue = undefined;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is a "magic" `teactOrderKey` property that tells us the element is updated
|
// Now we check if a preserved node was moved within preserved list
|
||||||
const order = 'props' in $newChild ? $newChild.props.teactOrderKey : undefined;
|
const newOrderKey = 'props' in $newChild ? $newChild.props.teactOrderKey : undefined;
|
||||||
const shouldMoveNode = currentChildInfo.index !== currentPreservedIndex && currentChildInfo.order !== order;
|
// That is indicated by a changed `teactOrderKey` value
|
||||||
|
const shouldMoveNode = (
|
||||||
|
currentChildInfo.index !== currentPreservedIndex && currentChildInfo.orderKey !== newOrderKey
|
||||||
|
);
|
||||||
const isMovingDown = shouldMoveNode && currentPreservedIndex > currentChildInfo.index;
|
const isMovingDown = shouldMoveNode && currentPreservedIndex > currentChildInfo.index;
|
||||||
|
|
||||||
// When the node goes down, preserved indexing actually breaks, so the "magic" should help.
|
|
||||||
if (!shouldMoveNode || isMovingDown) {
|
if (!shouldMoveNode || isMovingDown) {
|
||||||
currentPreservedIndex++;
|
currentPreservedIndex++;
|
||||||
}
|
}
|
||||||
@ -386,34 +392,33 @@ function renderFastListChildren($current: VirtualRealElement, $new: VirtualRealE
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (fragmentQueue) {
|
// This appends new children to the bottom
|
||||||
newChildren = newChildren.concat(flushFragmentQueue(fragmentQueue, fragmentIndex!, currentEl, $new));
|
if (fragmentElements) {
|
||||||
|
newChildren = newChildren.concat(renderFragment(fragmentElements, fragmentIndex!, currentEl, $new));
|
||||||
}
|
}
|
||||||
|
|
||||||
return newChildren;
|
return newChildren;
|
||||||
}
|
}
|
||||||
|
|
||||||
function flushFragmentQueue(
|
function renderFragment(
|
||||||
fragmentQueue: VirtualElement[], fragmentIndex: number, parentEl: HTMLElement, $parent: VirtualRealElement,
|
elements: VirtualElement[], fragmentIndex: number, parentEl: HTMLElement, $parent: VirtualRealElement,
|
||||||
) {
|
) {
|
||||||
if (fragmentQueue.length === 1) {
|
if (elements.length === 1) {
|
||||||
return [renderWithVirtual(parentEl, undefined, fragmentQueue[0], $parent, fragmentIndex, { forceIndex: true })!];
|
return [renderWithVirtual(parentEl, undefined, elements[0], $parent, fragmentIndex, { forceIndex: true })!];
|
||||||
} else if (fragmentQueue.length > 1) {
|
|
||||||
const fragment = document.createDocumentFragment();
|
|
||||||
const newChildren = fragmentQueue.map(($fragmentChild) => (
|
|
||||||
renderWithVirtual(parentEl, undefined, $fragmentChild, $parent, fragmentIndex!, { fragment })!
|
|
||||||
));
|
|
||||||
|
|
||||||
if (parentEl.childNodes[fragmentIndex]) {
|
|
||||||
parentEl.insertBefore(fragment, parentEl.childNodes[fragmentIndex]);
|
|
||||||
} else {
|
|
||||||
parentEl.appendChild(fragment);
|
|
||||||
}
|
|
||||||
|
|
||||||
return newChildren;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new Error('Unexpected input');
|
const fragment = document.createDocumentFragment();
|
||||||
|
const newChildren = elements.map(($element) => (
|
||||||
|
renderWithVirtual(parentEl, undefined, $element, $parent, fragmentIndex, { fragment })!
|
||||||
|
));
|
||||||
|
|
||||||
|
if (parentEl.childNodes[fragmentIndex]) {
|
||||||
|
parentEl.insertBefore(fragment, parentEl.childNodes[fragmentIndex]);
|
||||||
|
} else {
|
||||||
|
parentEl.appendChild(fragment);
|
||||||
|
}
|
||||||
|
|
||||||
|
return newChildren;
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateAttributes($current: VirtualRealElement, $new: VirtualRealElement, element: HTMLElement) {
|
function updateAttributes($current: VirtualRealElement, $new: VirtualRealElement, element: HTMLElement) {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user