[Perf] Teact: Fix memory leaks

This commit is contained in:
Alexander Zinchuk 2022-01-21 17:29:48 +01:00
parent 3a282b8086
commit 29852d2bf6
3 changed files with 51 additions and 36 deletions

View File

@ -60,7 +60,11 @@ export default function useScrollHooks(
return;
}
const { offsetHeight, scrollHeight, scrollTop } = containerRef.current!;
if (!containerRef.current) {
return;
}
const { offsetHeight, scrollHeight, scrollTop } = containerRef.current;
const scrollBottom = Math.round(scrollHeight - scrollTop - offsetHeight);
const isNearBottom = scrollBottom <= FAB_THRESHOLD;
const isAtBottom = scrollBottom <= NOTCH_THRESHOLD;

View File

@ -122,14 +122,20 @@ function renderWithVirtual(
unmountTree($current);
} else {
const areComponents = isComponentElement($current) && isComponentElement($new);
const currentTarget = getTarget($current);
if (!areComponents) {
setTarget($new, getTarget($current)!);
setTarget($new, currentTarget!);
setTarget($current, undefined as any); // Help GC
if ('props' in $current && 'props' in $new) {
$new.props.ref = $current.props.ref;
}
}
if (isRealElement($current) && isRealElement($new)) {
if (moveDirection) {
const node = getTarget($current)!;
const node = currentTarget!;
const nextSibling = parentEl.childNodes[moveDirection === 'up' ? index : index + 1];
if (nextSibling) {
@ -140,13 +146,13 @@ function renderWithVirtual(
}
if (!areComponents) {
updateAttributes($current, $new, getTarget($current) as HTMLElement);
updateAttributes($current, $new, currentTarget as HTMLElement);
}
$new.children = renderChildren(
$current,
$new,
areComponents ? parentEl : getTarget($current) as HTMLElement,
areComponents ? parentEl : currentTarget as HTMLElement,
);
}
}

View File

@ -336,17 +336,26 @@ export function hasElementChanged($old: VirtualElement, $new: VirtualElement) {
}
export function unmountTree($element: VirtualElement) {
if (!isRealElement($element)) {
return;
}
if (isComponentElement($element)) {
unmountComponent($element.componentInstance);
} else if ($element.target) {
removeAllDelegatedListeners($element.target as HTMLElement);
// Trying to help GC
// eslint-disable-next-line no-null/no-null
$element.target = null as any;
} else {
if (isTagElement($element)) {
if ($element.target) {
removeAllDelegatedListeners($element.target as HTMLElement);
}
if ($element.props.ref) {
$element.props.ref.current = undefined; // Help GC
}
}
if ($element.target) {
$element.target = undefined; // Help GC
}
if (!isRealElement($element)) {
return;
}
}
$element.children.forEach(unmountTree);
@ -363,9 +372,9 @@ function unmountComponent(componentInstance: ComponentInstance) {
return;
}
componentInstance.hooks.memos.byCursor.forEach((hook) => {
// eslint-disable-next-line no-null/no-null
hook.current = null;
// We need to clean refs before running effect cleanups
componentInstance.hooks.memos.byCursor.forEach((memoContainer) => {
memoContainer.current = undefined;
});
componentInstance.hooks.effects.byCursor.forEach(({ cleanup }) => {
@ -383,35 +392,31 @@ function unmountComponent(componentInstance: ComponentInstance) {
helpGc(componentInstance);
}
// We need to remove all references to DOM objects. We also clean all other references, just in case.
// We need to remove all references to DOM objects. We also clean all other references, just in case
function helpGc(componentInstance: ComponentInstance) {
/* eslint-disable no-null/no-null */
componentInstance.hooks.effects.byCursor.forEach((hook) => {
hook.cleanup = null as any;
hook.effect = null as any;
hook.dependencies = null as any;
hook.cleanup = undefined;
hook.effect = undefined as any;
hook.dependencies = undefined;
});
componentInstance.hooks.state.byCursor.forEach((hook) => {
hook.value = null as any;
hook.nextValue = null as any;
hook.setter = null as any;
hook.value = undefined;
hook.nextValue = undefined;
hook.setter = undefined as any;
});
componentInstance.hooks.memos.byCursor.forEach((hook) => {
hook.dependencies = null as any;
hook.dependencies = undefined as any;
});
componentInstance.hooks = null as any;
componentInstance.$element = null as any;
componentInstance.renderedValue = null as any;
componentInstance.Component = null as any;
componentInstance.props = null as any;
componentInstance.forceUpdate = null as any;
componentInstance.onUpdate = null as any;
/* eslint-enable no-null/no-null */
componentInstance.hooks = undefined as any;
componentInstance.$element = undefined as any;
componentInstance.renderedValue = undefined;
componentInstance.Component = undefined as any;
componentInstance.props = undefined as any;
componentInstance.forceUpdate = undefined;
componentInstance.onUpdate = undefined;
}
function prepareComponentForFrame(componentInstance: ComponentInstance) {