Update Teact from MyTonWallet

This commit is contained in:
Alexander Zinchuk 2025-11-22 12:54:10 +01:00
parent c59c6f02b5
commit 6054536ee9
2 changed files with 59 additions and 49 deletions

View File

@ -12,7 +12,6 @@ import type {
} from './teact'; } from './teact';
import { DEBUG } from '../../config'; import { DEBUG } from '../../config';
import { unique } from '../../util/iteratees';
import { addEventListener, removeAllDelegatedListeners, removeEventListener } from './dom-events'; import { addEventListener, removeAllDelegatedListeners, removeEventListener } from './dom-events';
import { import {
captureImmediateEffects, captureImmediateEffects,
@ -59,6 +58,8 @@ const headsByElement = new WeakMap<Element, VirtualDomHead>();
const extraClasses = new WeakMap<Element, Set<string>>(); const extraClasses = new WeakMap<Element, Set<string>>();
const extraStyles = new WeakMap<Element, Record<string, string>>(); const extraStyles = new WeakMap<Element, Record<string, string>>();
const uniqueChildKeysCache = new WeakMap<VirtualElementChildren, (keyof any)[]>();
let DEBUG_virtualTreeSize = 1; let DEBUG_virtualTreeSize = 1;
function render($element: VirtualElement | undefined, parentEl: HTMLElement) { function render($element: VirtualElement | undefined, parentEl: HTMLElement) {
@ -459,10 +460,6 @@ function renderChildren(
forceMoveToEnd = false, forceMoveToEnd = false,
namespace?: string, namespace?: string,
) { ) {
if (DEBUG) {
DEBUG_checkKeyUniqueness($new.children);
}
if (('props' in $new) && $new.props.teactFastList) { if (('props' in $new) && $new.props.teactFastList) {
renderFastListChildren($current, $new, currentContext, currentEl); renderFastListChildren($current, $new, currentContext, currentEl);
return; return;
@ -532,52 +529,31 @@ function renderFastListChildren(
const currentChildren = $current.children; const currentChildren = $current.children;
const newChildren = $new.children; const newChildren = $new.children;
const newKeys = new Set(); // Clear out duplicated keys to avoid incorrect elements matching
for (const $newChild of newChildren) { const currentKeysByIndex = getChildKeysByIndex(currentChildren);
const key = 'props' in $newChild ? $newChild.props.key : undefined; const newKeysByIndex = getChildKeysByIndex(newChildren);
const newKeys = new Set(newKeysByIndex);
if (DEBUG && isParentElement($newChild)) {
// eslint-disable-next-line no-null/no-null
if (key === undefined || key === null) {
// eslint-disable-next-line no-console
console.warn('Missing `key` in `teactFastList`');
}
if (DEBUG) {
for (const $newChild of newChildren) {
if ($newChild.type === VirtualType.Fragment) { if ($newChild.type === VirtualType.Fragment) {
throw new Error('[Teact] Fragment can not be child of container with `teactFastList`'); throw new Error('[Teact] Fragment can not be child of container with `teactFastList`');
} }
} }
newKeys.add(key);
} }
// Build a collection of old children that also remain in the new list // Build a collection of old children that also remain in the new list
let currentRemainingIndex = 0; let currentRemainingIndex = 0;
const remainingByKey: Record<string, { $element: VirtualElement; index: number; orderKey?: number }> = {}; const remainingByKey: Record<keyof any, { $element: VirtualElement; index: number; orderKey?: number }> = {};
for (let i = 0, l = currentChildren.length; i < l; i++) { for (let i = 0, l = currentChildren.length; i < l; i++) {
const $currentChild = currentChildren[i]; const $currentChild = currentChildren[i];
const key = currentKeysByIndex[i];
let key = 'props' in $currentChild ? $currentChild.props.key : undefined;
// eslint-disable-next-line no-null/no-null
const isKeyPresent = key !== undefined && key !== null;
// First we process removed children // First we process removed children
if (isKeyPresent && !newKeys.has(key)) { if (!newKeys.has(key)) {
renderWithVirtual(currentEl, $currentChild, undefined, $new, currentContext, -1); renderWithVirtual(currentEl, $currentChild, undefined, $new, currentContext, -1);
continue; continue;
} else if (!isKeyPresent) {
const $newChild = newChildren[i];
const newChildKey = ($newChild && 'props' in $newChild) ? $newChild.props.key : undefined;
// If a non-key element remains at the same index we preserve it with a virtual `key`
if ($newChild && !newChildKey) {
key = `${INDEX_KEY_PREFIX}${i}`;
// Otherwise, we just remove it
} else {
renderWithVirtual(currentEl, $currentChild, undefined, $new, currentContext, -1);
continue;
}
} }
// Then we build up info about remaining children // Then we build up info about remaining children
@ -595,7 +571,7 @@ function renderFastListChildren(
for (let i = 0, l = newChildren.length; i < l; i++) { for (let i = 0, l = newChildren.length; i < l; i++) {
const $newChild = newChildren[i]; const $newChild = newChildren[i];
const key = 'props' in $newChild ? $newChild.props.key : `${INDEX_KEY_PREFIX}${i}`; const key = newKeysByIndex[i];
const currentChildInfo = remainingByKey[key]; const currentChildInfo = remainingByKey[key];
if (!currentChildInfo) { if (!currentChildInfo) {
@ -932,23 +908,57 @@ function DEBUG_addToVirtualTreeSize($current: VirtualElementParent | VirtualDomH
}); });
} }
function DEBUG_checkKeyUniqueness(children: VirtualElementChildren) { /** Returns unique and not missing key for each child */
const firstChild = children[0]; function getChildKeysByIndex(children: VirtualElementChildren) {
if (firstChild && 'props' in firstChild && firstChild.props.key !== undefined) { // The caching makes sense, because each children list is handled at least twice (as the new and the current children)
const keys = children.reduce((acc: any[], child) => { let uniqueKeysByIndex = uniqueChildKeysCache.get(children);
if ('props' in child && child.props.key) { if (uniqueKeysByIndex) return uniqueKeysByIndex;
acc.push(child.props.key);
const seenKeys = new Set<any>();
const DEBUG_duplicatedKeys = new Set<any>();
uniqueKeysByIndex = children.map(($child, index) => {
let key = getElementKey($child);
if (isNullable(key)) {
if (DEBUG && isParentElement($child)) {
// eslint-disable-next-line no-console
console.warn('Missing `key` in `teactFastList`');
} }
return acc; key = `${INDEX_KEY_PREFIX}${index}`;
}, []); } else {
if (seenKeys.has(key)) {
if (DEBUG) {
DEBUG_duplicatedKeys.add(key);
}
if (keys.length !== unique(keys).length) { key = `${INDEX_KEY_PREFIX}${index}`;
// eslint-disable-next-line no-console } else {
console.warn('[Teact] Duplicated keys:', keys.filter((e, i, a) => a.indexOf(e) !== i), children); seenKeys.add(key);
throw new Error('[Teact] Children keys are not unique'); }
} }
return key;
});
if (DEBUG && DEBUG_duplicatedKeys.size) {
// eslint-disable-next-line no-console
console.warn('[Teact] Duplicated keys:', [...DEBUG_duplicatedKeys], children);
throw new Error('[Teact] Children keys are not unique');
} }
uniqueChildKeysCache.set(children, uniqueKeysByIndex);
return uniqueKeysByIndex;
}
function getElementKey($element: VirtualElement) {
return 'props' in $element ? $element.props.key : undefined;
}
function isNullable(value: unknown): value is undefined | null {
// eslint-disable-next-line no-null/no-null
return value === undefined || value === null;
} }
const TeactDOM = { render }; const TeactDOM = { render };

View File

@ -1,4 +1,3 @@
/* eslint-disable react-x/no-unnecessary-use-prefix */
import type { ReactElement } from 'react'; import type { ReactElement } from 'react';
import { DEBUG, DEBUG_MORE } from '../../config'; import { DEBUG, DEBUG_MORE } from '../../config';
@ -137,6 +136,7 @@ export type TeactNode =
| string | string
| number | number
| boolean | boolean
| undefined
| TeactNode[]; | TeactNode[];
type Effect = () => (NoneToVoidFunction | void); type Effect = () => (NoneToVoidFunction | void);