Teact: Properly clean up function refs

This commit is contained in:
Alexander Zinchuk 2023-06-16 12:44:46 +02:00
parent bab53d42ec
commit d8a258d092
2 changed files with 46 additions and 26 deletions

View File

@ -3,22 +3,38 @@ import React, { useEffect, useRef, useState } from '../../lib/teact/teact';
function TestUpdateRef() { function TestUpdateRef() {
const [isShown, setIsShown] = useState(true); const [isShown, setIsShown] = useState(true);
// eslint-disable-next-line no-null/no-null // eslint-disable-next-line no-null/no-null
const shownRef = useRef<HTMLInputElement>(null); const inputRef = useRef<HTMLInputElement>(null);
// eslint-disable-next-line no-null/no-null
const headingRef = useRef<HTMLDivElement>(null);
useEffect(() => { useEffect(() => {
// Input content should preserve, but ref should clean up // Input content should preserve, but ref should clean up
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.log('!!!', shownRef.current); console.log('!!!', inputRef.current);
// Ref should update
// eslint-disable-next-line no-console
console.log('!!!', headingRef.current);
}, [isShown]); }, [isShown]);
return ( return (
<div onClick={() => setIsShown(!isShown)}> <>
{isShown ? ( <div onClick={() => setIsShown(!isShown)}>
<input ref={shownRef} /> {isShown ? (
) : ( <input ref={inputRef} />
<input /> ) : (
)} <input />
</div> )}
</div>
<div onClick={() => setIsShown(!isShown)}>
{isShown ? (
<h1 ref={headingRef}>Shown</h1>
) : (
<h2 ref={headingRef}>Hidden</h2>
)}
</div>
</>
); );
} }

View File

@ -129,6 +129,10 @@ function renderWithVirtual<T extends VirtualElement | undefined>(
const node = createNode($newAsReal); const node = createNode($newAsReal);
$newAsReal.target = node; $newAsReal.target = node;
insertBefore(fragment || parentEl, node, nextSibling); insertBefore(fragment || parentEl, node, nextSibling);
if (isTagElement($newAsReal)) {
setElementRef($newAsReal, node as HTMLElement);
}
} }
} else if ($current && !$new) { } else if ($current && !$new) {
remount(parentEl, $current, undefined); remount(parentEl, $current, undefined);
@ -149,6 +153,10 @@ function renderWithVirtual<T extends VirtualElement | undefined>(
const node = createNode($newAsReal); const node = createNode($newAsReal);
$newAsReal.target = node; $newAsReal.target = node;
remount(parentEl, $current, node, nextSibling); remount(parentEl, $current, node, nextSibling);
if (isTagElement($newAsReal)) {
setElementRef($newAsReal, node as HTMLElement);
}
} }
} else { } else {
const isComponent = isCurrentComponent && isNewComponent; const isComponent = isCurrentComponent && isNewComponent;
@ -173,13 +181,8 @@ function renderWithVirtual<T extends VirtualElement | undefined>(
if (isTag) { if (isTag) {
const $newAsTag = $new as VirtualElementTag; const $newAsTag = $new as VirtualElementTag;
if ($current.props.ref?.current === currentTarget) { setElementRef($current, undefined);
$current.props.ref.current = undefined; setElementRef($newAsTag, currentTarget as HTMLElement);
}
if ($newAsTag.props.ref) {
$newAsTag.props.ref.current = currentTarget;
}
if (nextSibling || options.forceMoveToEnd) { if (nextSibling || options.forceMoveToEnd) {
insertBefore(parentEl, currentTarget, nextSibling); insertBefore(parentEl, currentTarget, nextSibling);
@ -280,12 +283,6 @@ function createNode($element: VirtualElementReal): Node {
const { tag, props, children = [] } = $element; const { tag, props, children = [] } = $element;
const element = document.createElement(tag); const element = document.createElement(tag);
if (typeof props.ref === 'object') {
props.ref.current = element;
} else if (typeof props.ref === 'function') {
props.ref(element);
}
processControlled(tag, props); processControlled(tag, props);
Object.entries(props).forEach(([key, value]) => { Object.entries(props).forEach(([key, value]) => {
@ -343,10 +340,7 @@ function unmountRealTree($element: VirtualElement) {
if (target) { if (target) {
extraClasses.delete(target); extraClasses.delete(target);
removeAllDelegatedListeners(target); removeAllDelegatedListeners(target);
setElementRef($element, undefined);
if ($element.props.ref?.current === target) {
$element.props.ref.current = undefined;
}
} }
} }
@ -559,6 +553,16 @@ function renderFragment(
return newChildren; return newChildren;
} }
function setElementRef($element: VirtualElementTag, htmlElement: HTMLElement | undefined) {
const { ref } = $element.props;
if (typeof ref === 'object') {
ref.current = htmlElement;
} else if (typeof ref === 'function') {
ref(htmlElement);
}
}
function processControlled(tag: string, props: AnyLiteral) { function processControlled(tag: string, props: AnyLiteral) {
// TODO Remove after tests // TODO Remove after tests
if (!props.teactExperimentControlled) { if (!props.teactExperimentControlled) {