[Refactoring] Teact: Add comments to teactFastList algorithm

This commit is contained in:
Alexander Zinchuk 2022-05-30 15:40:02 +04:00
parent 247d8a354b
commit d3e3cced6c

View File

@ -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) {