mirror of
https://github.com/danog/telegram-tt.git
synced 2024-11-29 20:29:12 +01:00
Teact: Fix fragment breaking other elements order
This commit is contained in:
parent
819280b191
commit
19ac35b676
35
src/components/test/TestFragment.tsx
Normal file
35
src/components/test/TestFragment.tsx
Normal file
@ -0,0 +1,35 @@
|
||||
import React, { useRef, useState } from '../../lib/teact/teact';
|
||||
|
||||
export function App() {
|
||||
const [trigger, setTrigger] = useState(false);
|
||||
|
||||
return (
|
||||
<div
|
||||
className="App"
|
||||
onClick={() => {
|
||||
setTrigger((current) => !current);
|
||||
}}
|
||||
>
|
||||
<h2>Click to update</h2>
|
||||
{trigger ? (
|
||||
<>
|
||||
<span>fragment</span>
|
||||
<span>content</span>
|
||||
</>
|
||||
) : undefined}
|
||||
<Child />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Child() {
|
||||
const idRef = useRef(String(Math.random()).slice(-4));
|
||||
|
||||
return (
|
||||
<div>
|
||||
This number should never change: {idRef.current}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
@ -5,6 +5,7 @@ import type {
|
||||
VirtualElementParent,
|
||||
VirtualElementChildren,
|
||||
VirtualElementReal,
|
||||
VirtualElementFragment,
|
||||
} from './teact';
|
||||
import {
|
||||
hasElementChanged,
|
||||
@ -16,6 +17,7 @@ import {
|
||||
mountComponent,
|
||||
renderComponent,
|
||||
unmountComponent,
|
||||
isFragmentElement,
|
||||
} from './teact';
|
||||
import generateIdFor from '../../util/generateIdFor';
|
||||
import { DEBUG } from '../../config';
|
||||
@ -80,6 +82,9 @@ function renderWithVirtual<T extends VirtualElement | undefined>(
|
||||
const isNewComponent = $new && isComponentElement($new);
|
||||
const $newAsReal = $new as VirtualElementReal;
|
||||
|
||||
const isCurrentFragment = $current && !isCurrentComponent && isFragmentElement($current);
|
||||
const isNewFragment = $new && !isNewComponent && isFragmentElement($new);
|
||||
|
||||
if (
|
||||
!skipComponentUpdate
|
||||
&& isCurrentComponent && isNewComponent
|
||||
@ -105,9 +110,12 @@ function renderWithVirtual<T extends VirtualElement | undefined>(
|
||||
}
|
||||
|
||||
if (!$current && $new) {
|
||||
if (isNewComponent) {
|
||||
$new = initComponent(parentEl, $new as VirtualElementComponent, $parent, index) as typeof $new;
|
||||
mountComponentChildren(parentEl, $new as VirtualElementComponent, { nextSibling, fragment });
|
||||
if (isNewComponent || isNewFragment) {
|
||||
if (isNewComponent) {
|
||||
$new = initComponent(parentEl, $new as VirtualElementComponent, $parent, index) as typeof $new;
|
||||
}
|
||||
|
||||
mountChildren(parentEl, $new as VirtualElementComponent | VirtualElementFragment, { nextSibling, fragment });
|
||||
} else {
|
||||
const node = createNode($newAsReal);
|
||||
$newAsReal.target = node;
|
||||
@ -121,10 +129,13 @@ function renderWithVirtual<T extends VirtualElement | undefined>(
|
||||
nextSibling = getNextSibling($current);
|
||||
}
|
||||
|
||||
if (isNewComponent) {
|
||||
$new = initComponent(parentEl, $new as VirtualElementComponent, $parent, index) as typeof $new;
|
||||
if (isNewComponent || isNewFragment) {
|
||||
if (isNewComponent) {
|
||||
$new = initComponent(parentEl, $new as VirtualElementComponent, $parent, index) as typeof $new;
|
||||
}
|
||||
|
||||
remount(parentEl, $current, undefined);
|
||||
mountComponentChildren(parentEl, $new as VirtualElementComponent, { nextSibling, fragment });
|
||||
mountChildren(parentEl, $new as VirtualElementComponent | VirtualElementFragment, { nextSibling, fragment });
|
||||
} else {
|
||||
const node = createNode($newAsReal);
|
||||
$newAsReal.target = node;
|
||||
@ -132,10 +143,12 @@ function renderWithVirtual<T extends VirtualElement | undefined>(
|
||||
}
|
||||
} else {
|
||||
const isComponent = isCurrentComponent && isNewComponent;
|
||||
if (isComponent) {
|
||||
($new as VirtualElementComponent).children = renderChildren(
|
||||
const isFragment = isCurrentFragment && isNewFragment;
|
||||
|
||||
if (isComponent || isFragment) {
|
||||
($new as VirtualElementComponent | VirtualElementFragment).children = renderChildren(
|
||||
$current,
|
||||
$new as VirtualElementComponent,
|
||||
$new as VirtualElementComponent | VirtualElementFragment,
|
||||
parentEl,
|
||||
nextSibling,
|
||||
);
|
||||
@ -220,16 +233,20 @@ function setupComponentUpdateListener(
|
||||
};
|
||||
}
|
||||
|
||||
function mountComponentChildren(parentEl: HTMLElement, $element: VirtualElementComponent, options: {
|
||||
nextSibling?: ChildNode;
|
||||
fragment?: DocumentFragment;
|
||||
}) {
|
||||
function mountChildren(
|
||||
parentEl: HTMLElement,
|
||||
$element: VirtualElementComponent | VirtualElementFragment,
|
||||
options: {
|
||||
nextSibling?: ChildNode;
|
||||
fragment?: DocumentFragment;
|
||||
},
|
||||
) {
|
||||
$element.children = $element.children.map(($child, i) => {
|
||||
return renderWithVirtual(parentEl, undefined, $child, $element, i, options);
|
||||
});
|
||||
}
|
||||
|
||||
function unmountComponentChildren(parentEl: HTMLElement, $element: VirtualElementComponent) {
|
||||
function unmountChildren(parentEl: HTMLElement, $element: VirtualElementComponent | VirtualElementFragment) {
|
||||
$element.children.forEach(($child) => {
|
||||
renderWithVirtual(parentEl, $child, undefined, $element, -1);
|
||||
});
|
||||
@ -274,9 +291,15 @@ function remount(
|
||||
node: Node | undefined,
|
||||
componentNextSibling?: ChildNode,
|
||||
) {
|
||||
if (isComponentElement($current)) {
|
||||
unmountComponent($current.componentInstance);
|
||||
unmountComponentChildren(parentEl, $current);
|
||||
const isComponent = isComponentElement($current);
|
||||
const isFragment = !isComponent && isFragmentElement($current);
|
||||
|
||||
if (isComponent || isFragment) {
|
||||
if (isComponent) {
|
||||
unmountComponent($current.componentInstance);
|
||||
}
|
||||
|
||||
unmountChildren(parentEl, $current);
|
||||
|
||||
if (node) {
|
||||
insertBefore(parentEl, node, componentNextSibling);
|
||||
@ -327,7 +350,7 @@ function insertBefore(parentEl: HTMLElement | DocumentFragment, node: Node, next
|
||||
}
|
||||
|
||||
function getNextSibling($current: VirtualElement): ChildNode | undefined {
|
||||
if (isComponentElement($current)) {
|
||||
if (isComponentElement($current) || isFragmentElement($current)) {
|
||||
const lastChild = $current.children[$current.children.length - 1];
|
||||
return getNextSibling(lastChild);
|
||||
}
|
||||
@ -344,7 +367,7 @@ function renderChildren(
|
||||
DEBUG_checkKeyUniqueness($new.children);
|
||||
}
|
||||
|
||||
if ($new.props.teactFastList) {
|
||||
if (('props' in $new) && $new.props.teactFastList) {
|
||||
return renderFastListChildren($current, $new, currentEl);
|
||||
}
|
||||
|
||||
@ -388,10 +411,16 @@ function renderFastListChildren($current: VirtualElementParent, $new: VirtualEle
|
||||
$new.children.map(($newChild) => {
|
||||
const key = 'props' in $newChild && $newChild.props.key;
|
||||
|
||||
// eslint-disable-next-line no-null/no-null
|
||||
if (DEBUG && isParentElement($newChild) && (key === undefined || key === null)) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn('Missing `key` in `teactFastList`');
|
||||
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 (isFragmentElement($newChild)) {
|
||||
throw new Error('[Teact] Fragment can not be child of container with `teactFastList`');
|
||||
}
|
||||
}
|
||||
|
||||
return key;
|
||||
@ -555,7 +584,7 @@ function processUncontrolledOnMount(element: HTMLElement, props: AnyLiteral) {
|
||||
}
|
||||
}
|
||||
|
||||
function updateAttributes($current: VirtualElementParent, $new: VirtualElementParent, element: HTMLElement) {
|
||||
function updateAttributes($current: VirtualElementTag, $new: VirtualElementTag, element: HTMLElement) {
|
||||
processControlled(element.tagName, $new.props);
|
||||
|
||||
const currentEntries = Object.entries($current.props);
|
||||
|
@ -19,6 +19,7 @@ export enum VirtualElementTypesEnum {
|
||||
Text,
|
||||
Tag,
|
||||
Component,
|
||||
Fragment,
|
||||
}
|
||||
|
||||
interface VirtualElementEmpty {
|
||||
@ -47,6 +48,12 @@ export interface VirtualElementComponent {
|
||||
children: VirtualElementChildren;
|
||||
}
|
||||
|
||||
export interface VirtualElementFragment {
|
||||
type: VirtualElementTypesEnum.Fragment;
|
||||
target?: Node;
|
||||
children: VirtualElementChildren;
|
||||
}
|
||||
|
||||
export type StateHookSetter<T> = (newValue: ((current: T) => T) | T) => void;
|
||||
|
||||
interface ComponentInstance {
|
||||
@ -96,12 +103,14 @@ export type VirtualElement =
|
||||
VirtualElementEmpty
|
||||
| VirtualElementText
|
||||
| VirtualElementTag
|
||||
| VirtualElementComponent;
|
||||
| VirtualElementComponent
|
||||
| VirtualElementFragment;
|
||||
export type VirtualElementParent =
|
||||
VirtualElementTag
|
||||
| VirtualElementComponent;
|
||||
| VirtualElementComponent
|
||||
| VirtualElementFragment;
|
||||
export type VirtualElementChildren = VirtualElement[];
|
||||
export type VirtualElementReal = Exclude<VirtualElement, VirtualElementComponent>;
|
||||
export type VirtualElementReal = Exclude<VirtualElement, VirtualElementComponent | VirtualElementFragment>;
|
||||
|
||||
// Compatibility with JSX types
|
||||
export type TeactNode =
|
||||
@ -135,8 +144,12 @@ export function isComponentElement($element: VirtualElement): $element is Virtua
|
||||
return $element.type === VirtualElementTypesEnum.Component;
|
||||
}
|
||||
|
||||
export function isFragmentElement($element: VirtualElement): $element is VirtualElementFragment {
|
||||
return $element.type === VirtualElementTypesEnum.Fragment;
|
||||
}
|
||||
|
||||
export function isParentElement($element: VirtualElement): $element is VirtualElementParent {
|
||||
return isTagElement($element) || isComponentElement($element);
|
||||
return isTagElement($element) || isComponentElement($element) || isFragmentElement($element);
|
||||
}
|
||||
|
||||
function createElement(
|
||||
@ -144,21 +157,24 @@ function createElement(
|
||||
props: Props,
|
||||
...children: any[]
|
||||
): VirtualElementParent | VirtualElementChildren {
|
||||
if (!props) {
|
||||
props = {};
|
||||
}
|
||||
|
||||
children = children.flat();
|
||||
|
||||
if (source === Fragment) {
|
||||
return children;
|
||||
return buildFragmentElement(children);
|
||||
} else if (typeof source === 'function') {
|
||||
return createComponentInstance(source, props, children);
|
||||
return createComponentInstance(source, props || {}, children);
|
||||
} else {
|
||||
return buildTagElement(source, props, children);
|
||||
return buildTagElement(source, props || {}, children);
|
||||
}
|
||||
}
|
||||
|
||||
function buildFragmentElement(children: any[]): VirtualElementFragment {
|
||||
return {
|
||||
type: VirtualElementTypesEnum.Fragment,
|
||||
children: dropEmptyTail(children).map(buildChildElement),
|
||||
};
|
||||
}
|
||||
|
||||
function createComponentInstance(Component: FC, props: Props, children: any[]): VirtualElementComponent {
|
||||
let parsedChildren: any | any[] | undefined;
|
||||
if (children.length === 0) {
|
||||
|
Loading…
Reference in New Issue
Block a user