Teact: Store last rendered namespace for components (#6570)

This commit is contained in:
zubiden 2026-01-13 01:14:11 +01:00 committed by Alexander Zinchuk
parent d14da19bd8
commit 4819cf02b3
4 changed files with 67 additions and 16 deletions

View File

@ -24,7 +24,7 @@ import usePrevious from '../hooks/usePrevious';
import { useSignalEffect } from '../hooks/useSignalEffect';
import { getIsInBackground } from '../hooks/window/useBackgroundMode';
// import Test from './test/TestLocale';
// import Test from './test/TestSvg';
import Auth from './auth/Auth';
import Notifications from './common/Notifications';
import UiLoader from './common/UiLoader';

View File

@ -1,5 +1,7 @@
import { useState } from '../../lib/teact/teact';
import useInterval from '../../hooks/schedulers/useInterval';
export function App() {
const [stateValue, setStateValue] = useState(false);
@ -43,9 +45,33 @@ export function App() {
stroke-width="10"
fill="none"
/>
<NestedSvg />
<>
<rect x="0" y="0" width="50" height="50" fill="blue" />
<rect x="50" y="0" width="50" height="50" fill={stateValue ? 'red' : 'green'} />
</>
</svg>
</div>
);
}
function NestedSvg() {
const [stateValue, setStateValue] = useState(false);
useInterval(() => {
setStateValue((current) => !current);
}, 1000);
return (
<circle
cx="60"
cy="60"
r="10"
fill={stateValue ? 'red' : 'blue'}
/>
);
}
export default App;

View File

@ -84,6 +84,14 @@ function render($element: VirtualElement | undefined, parentEl: HTMLElement) {
return undefined;
}
interface RenderWithVirtualOptions {
skipComponentUpdate?: boolean;
nextSibling?: ChildNode;
forceMoveToEnd?: boolean;
fragment?: DocumentFragment;
namespace?: string;
}
function renderWithVirtual<T extends VirtualElement | undefined>(
parentEl: DOMElement,
$current: VirtualElement | undefined,
@ -91,13 +99,7 @@ function renderWithVirtual<T extends VirtualElement | undefined>(
$parent: VirtualElementParent | VirtualDomHead,
currentContext: CurrentContext,
index: number,
options: {
skipComponentUpdate?: boolean;
nextSibling?: ChildNode;
forceMoveToEnd?: boolean;
fragment?: DocumentFragment;
namespace?: string;
} = {},
options: RenderWithVirtualOptions = {},
): T {
const { skipComponentUpdate, fragment } = options;
let { nextSibling, namespace } = options;
@ -114,6 +116,10 @@ function renderWithVirtual<T extends VirtualElement | undefined>(
if ($new.props.xmlns) namespace = $new.props.xmlns;
}
if ($new?.type === VirtualType.Component) {
$new.componentInstance.lastMountNamespace = namespace;
}
if (
!skipComponentUpdate
&& isCurrentComponent && isNewComponent
@ -215,6 +221,7 @@ function renderWithVirtual<T extends VirtualElement | undefined>(
parentEl,
nextSibling,
options.forceMoveToEnd,
namespace,
);
} else {
const $currentAsReal = $current as VirtualElementReal;
@ -288,7 +295,10 @@ function setupComponentUpdateListener(
$parent,
currentContext,
index,
{ skipComponentUpdate: true },
{
skipComponentUpdate: true,
namespace: componentInstance.lastMountNamespace,
},
);
};
}
@ -461,7 +471,7 @@ function renderChildren(
namespace?: string,
) {
if (('props' in $new) && $new.props.teactFastList) {
renderFastListChildren($current, $new, currentContext, currentEl);
renderFastListChildren($current, $new, currentContext, currentEl, namespace);
return;
}
@ -524,7 +534,11 @@ function renderChildren(
// 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: VirtualElementParent, $new: VirtualElementParent, currentContext: CurrentContext, currentEl: DOMElement,
$current: VirtualElementParent,
$new: VirtualElementParent,
currentContext: CurrentContext,
currentEl: DOMElement,
namespace?: string,
) {
const currentChildren = $current.children;
const newChildren = $new.children;
@ -586,7 +600,7 @@ function renderFastListChildren(
// This prepends new children to the top
if (fragmentSize) {
renderFragment(fragmentIndex!, fragmentSize, currentEl, $new, currentContext);
renderFragment(fragmentIndex!, fragmentSize, currentEl, $new, currentContext, namespace);
fragmentSize = undefined;
fragmentIndex = undefined;
}
@ -604,7 +618,14 @@ function renderFastListChildren(
}
const nextSibling = currentEl.childNodes[isMovingDown ? i + 1 : i];
const options = shouldMoveNode ? (nextSibling ? { nextSibling } : { forceMoveToEnd: true }) : undefined;
const options: RenderWithVirtualOptions = { namespace };
if (shouldMoveNode) {
if (nextSibling) {
options.nextSibling = nextSibling;
} else {
options.forceMoveToEnd = true;
}
}
const $renderedChild = renderWithVirtual(
currentEl, currentChildInfo.$element, $newChild, $new, currentContext, i, options,
@ -616,7 +637,7 @@ function renderFastListChildren(
// This appends new children to the bottom
if (fragmentSize) {
renderFragment(fragmentIndex!, fragmentSize, currentEl, $new, currentContext);
renderFragment(fragmentIndex!, fragmentSize, currentEl, $new, currentContext, namespace);
}
}
@ -626,13 +647,14 @@ function renderFragment(
parentEl: DOMElement,
$parent: VirtualElementParent,
currentContext: CurrentContext,
namespace?: string,
) {
const nextSibling = parentEl.childNodes[fragmentIndex];
if (fragmentSize === 1) {
const $child = $parent.children[fragmentIndex];
const $renderedChild = renderWithVirtual(
parentEl, undefined, $child, $parent, currentContext, fragmentIndex, { nextSibling },
parentEl, undefined, $child, $parent, currentContext, fragmentIndex, { nextSibling, namespace },
);
if ($renderedChild !== $child) {
$parent.children[fragmentIndex] = $renderedChild;
@ -645,7 +667,9 @@ function renderFragment(
for (let i = fragmentIndex; i < fragmentIndex + fragmentSize; i++) {
const $child = $parent.children[i];
const $renderedChild = renderWithVirtual(parentEl, undefined, $child, $parent, currentContext, i, { fragment });
const $renderedChild = renderWithVirtual(
parentEl, undefined, $child, $parent, currentContext, i, { fragment, namespace },
);
if ($renderedChild !== $child) {
$parent.children[i] = $renderedChild;
}

View File

@ -80,6 +80,7 @@ interface ComponentInstance {
name: string;
props: Props;
renderedValue?: any;
lastMountNamespace?: string;
mountState: MountState;
context?: Record<string, Signal<unknown>>;
hooks?: {