From 28ac8fe061ed44420427d08e80aca492421546fa Mon Sep 17 00:00:00 2001 From: Alexander Zinchuk Date: Thu, 6 Jul 2023 14:25:38 +0200 Subject: [PATCH] Revert "[Perf] Teact: Optimizations" This reverts commit 691c6204c1908af0d3317cb18dc67efb566beefe. --- src/lib/teact/teact-dom.ts | 329 ++++++++++++++++--------------------- src/lib/teact/teact.ts | 215 +++++++++++------------- 2 files changed, 238 insertions(+), 306 deletions(-) diff --git a/src/lib/teact/teact-dom.ts b/src/lib/teact/teact-dom.ts index dfc40c9b7..be1327346 100644 --- a/src/lib/teact/teact-dom.ts +++ b/src/lib/teact/teact-dom.ts @@ -1,4 +1,3 @@ -import type { ChangeEvent } from 'react'; import type { VirtualElement, VirtualElementChildren, @@ -11,12 +10,16 @@ import type { import { captureImmediateEffects, hasElementChanged, + isComponentElement, + isEmptyElement, + isFragmentElement, isParentElement, + isTagElement, + isTextElement, mountComponent, MountState, renderComponent, unmountComponent, - VirtualType, } from './teact'; import { DEBUG } from '../../config'; import { addEventListener, removeAllDelegatedListeners, removeEventListener } from './dom-events'; @@ -55,10 +58,10 @@ function render($element: VirtualElement | undefined, parentEl: HTMLElement) { const runImmediateEffects = captureImmediateEffects(); const $head = headsByElement.get(parentEl)!; - const $renderedChild = renderWithVirtual(parentEl, $head.children[0], $element, $head, 0); + const $newElement = renderWithVirtual(parentEl, $head.children[0], $element, $head, 0); runImmediateEffects?.(); - $head.children = $renderedChild ? [$renderedChild] : []; + $head.children = $newElement ? [$newElement] : []; if (process.env.APP_ENV === 'perf') { DEBUG_virtualTreeSize = 0; @@ -86,12 +89,12 @@ function renderWithVirtual( const { skipComponentUpdate, fragment } = options; let { nextSibling } = options; - const isCurrentComponent = $current && $current.type === VirtualType.Component; - const isNewComponent = $new && $new.type === VirtualType.Component; + const isCurrentComponent = $current && isComponentElement($current); + const isNewComponent = $new && isComponentElement($new); const $newAsReal = $new as VirtualElementReal; - const isCurrentFragment = $current && !isCurrentComponent && $current.type === VirtualType.Fragment; - const isNewFragment = $new && !isNewComponent && $new.type === VirtualType.Fragment; + const isCurrentFragment = $current && !isCurrentComponent && isFragmentElement($current); + const isNewFragment = $new && !isNewComponent && isFragmentElement($new); if ( !skipComponentUpdate @@ -129,19 +132,12 @@ function renderWithVirtual( mountChildren(parentEl, $new as VirtualElementComponent | VirtualElementFragment, { nextSibling, fragment }); } else { - const canSetText = $parent.children.length === 1 && $newAsReal.type === VirtualType.Text; + const node = createNode($newAsReal); + $newAsReal.target = node; + insertBefore(fragment || parentEl, node, nextSibling); - if (canSetText) { - parentEl.textContent = 'value' in $newAsReal ? $newAsReal.value : ''; - $newAsReal.target = parentEl.firstChild!; - } else { - const node = createNode($newAsReal); - $newAsReal.target = node; - insertBefore(fragment || parentEl, node, nextSibling); - - if ($newAsReal.type === VirtualType.Tag) { - setElementRef($newAsReal, node as HTMLElement); - } + if (isTagElement($newAsReal)) { + setElementRef($newAsReal, node as HTMLElement); } } } else if ($current && !$new) { @@ -160,27 +156,12 @@ function renderWithVirtual( remount(parentEl, $current, undefined); mountChildren(parentEl, $new as VirtualElementComponent | VirtualElementFragment, { nextSibling, fragment }); } else { - const canSetText = $parent.children.length === 1 - && $newAsReal.type === VirtualType.Text - && ($current.type === VirtualType.Text || $current.type === VirtualType.Empty) - && (!parentEl.firstChild || parentEl.firstChild === $current.target); + const node = createNode($newAsReal); + $newAsReal.target = node; + remount(parentEl, $current, node, nextSibling); - if (canSetText) { - const value = 'value' in $newAsReal ? $newAsReal.value : ''; - if (parentEl.firstChild) { - parentEl.firstChild.nodeValue = value; - } else { - parentEl.textContent = value; - } - $newAsReal.target = parentEl.firstChild!; - } else { - const node = createNode($newAsReal); - $newAsReal.target = node; - remount(parentEl, $current, node, nextSibling); - - if ($newAsReal.type === VirtualType.Tag) { - setElementRef($newAsReal, node as HTMLElement); - } + if (isTagElement($newAsReal)) { + setElementRef($newAsReal, node as HTMLElement); } } } else { @@ -188,7 +169,7 @@ function renderWithVirtual( const isFragment = isCurrentFragment && isNewFragment; if (isComponent || isFragment) { - renderChildren( + ($new as VirtualElementComponent | VirtualElementFragment).children = renderChildren( $current, $new as VirtualElementComponent | VirtualElementFragment, parentEl, @@ -202,7 +183,7 @@ function renderWithVirtual( $newAsReal.target = currentTarget; $currentAsReal.target = undefined; // Help GC - const isTag = $current.type === VirtualType.Tag; + const isTag = isTagElement($current); if (isTag) { const $newAsTag = $new as VirtualElementTag; @@ -214,7 +195,12 @@ function renderWithVirtual( } updateAttributes($current, $newAsTag, currentTarget as HTMLElement); - renderChildren($current, $newAsTag, currentTarget as HTMLElement); + + $newAsTag.children = renderChildren( + $current, + $newAsTag, + currentTarget as HTMLElement, + ); } } } @@ -236,8 +222,8 @@ function initComponent( setupComponentUpdateListener(parentEl, $element, $parent, index); const $firstChild = $element.children[0]; - if ($firstChild.type === VirtualType.Component) { - $element.children[0] = initComponent(parentEl, $firstChild, $element, 0); + if (isComponentElement($firstChild)) { + $element.children = [initComponent(parentEl, $firstChild, $element, 0)]; } } @@ -278,54 +264,42 @@ function mountChildren( fragment?: DocumentFragment; }, ) { - const { children } = $element; - for (let i = 0, l = children.length; i < l; i++) { - const $child = children[i]; - const $renderedChild = renderWithVirtual(parentEl, undefined, $child, $element, i, options); - if ($renderedChild !== $child) { - children[i] = $renderedChild; - } - } + $element.children = $element.children.map(($child, i) => { + return renderWithVirtual(parentEl, undefined, $child, $element, i, options); + }); } function unmountChildren(parentEl: HTMLElement, $element: VirtualElementComponent | VirtualElementFragment) { - for (const $child of $element.children) { + $element.children.forEach(($child) => { renderWithVirtual(parentEl, $child, undefined, $element, -1); - } + }); } function createNode($element: VirtualElementReal): Node { - if ($element.type === VirtualType.Empty) { + if (isEmptyElement($element)) { return document.createTextNode(''); } - if ($element.type === VirtualType.Text) { + if (isTextElement($element)) { return document.createTextNode($element.value); } - const { tag, props, children } = $element; + const { tag, props, children = [] } = $element; const element = document.createElement(tag); processControlled(tag, props); - // eslint-disable-next-line no-restricted-syntax - for (const key in props) { - if (!props.hasOwnProperty(key)) continue; - + Object.entries(props).forEach(([key, value]) => { if (props[key] !== undefined) { - setAttribute(element, key, props[key]); + setAttribute(element, key, value); } - } + }); processUncontrolledOnMount(element, props); - for (let i = 0, l = children.length; i < l; i++) { - const $child = children[i]; - const $renderedChild = renderWithVirtual(element, undefined, $child, $element, i); - if ($renderedChild !== $child) { - children[i] = $renderedChild; - } - } + $element.children = children.map(($child, i) => ( + renderWithVirtual(element, undefined, $child, $element, i) + )); return element; } @@ -336,8 +310,8 @@ function remount( node: Node | undefined, componentNextSibling?: ChildNode, ) { - const isComponent = $current.type === VirtualType.Component; - const isFragment = !isComponent && $current.type === VirtualType.Fragment; + const isComponent = isComponentElement($current); + const isFragment = !isComponent && isFragmentElement($current); if (isComponent || isFragment) { if (isComponent) { @@ -361,25 +335,23 @@ function remount( } function unmountRealTree($element: VirtualElement) { - if ($element.type === VirtualType.Component) { + if (isComponentElement($element)) { unmountComponent($element.componentInstance); - } else if ($element.type !== VirtualType.Fragment) { - if ($element.type === VirtualType.Tag) { + } else if (!isFragmentElement($element)) { + if (isTagElement($element)) { extraClasses.delete($element.target!); - setElementRef($element, undefined); removeAllDelegatedListeners($element.target!); + setElementRef($element, undefined); } $element.target = undefined; // Help GC - if ($element.type !== VirtualType.Tag) { + if (!isParentElement($element)) { return; } } - for (const $child of $element.children) { - unmountRealTree($child); - } + $element.children.forEach(unmountRealTree); } function insertBefore(parentEl: HTMLElement | DocumentFragment, node: Node, nextSibling?: ChildNode) { @@ -391,7 +363,7 @@ function insertBefore(parentEl: HTMLElement | DocumentFragment, node: Node, next } function getNextSibling($current: VirtualElement): ChildNode | undefined { - if ($current.type === VirtualType.Component || $current.type === VirtualType.Fragment) { + if (isComponentElement($current) || isFragmentElement($current)) { const lastChild = $current.children[$current.children.length - 1]; return getNextSibling(lastChild); } @@ -411,16 +383,13 @@ function renderChildren( } if (('props' in $new) && $new.props.teactFastList) { - renderFastListChildren($current, $new, currentEl); - return; + return renderFastListChildren($current, $new, currentEl); } - const currentChildren = $current.children; - const newChildren = $new.children; - - const currentChildrenLength = currentChildren.length; - const newChildrenLength = newChildren.length; + const currentChildrenLength = $current.children.length; + const newChildrenLength = $new.children.length; const maxLength = Math.max(currentChildrenLength, newChildrenLength); + const newChildren = []; const fragment = newChildrenLength > currentChildrenLength ? document.createDocumentFragment() : undefined; const lastCurrentChild = $current.children[currentChildrenLength - 1]; @@ -429,111 +398,111 @@ function renderChildren( ); for (let i = 0; i < maxLength; i++) { - const $renderedChild = renderWithVirtual( + const $newChild = renderWithVirtual( currentEl, - currentChildren[i], - newChildren[i], + $current.children[i], + $new.children[i], $new, i, i >= currentChildrenLength ? { fragment } : { nextSibling, forceMoveToEnd }, ); - if ($renderedChild && $renderedChild !== newChildren[i]) { - newChildren[i] = $renderedChild; + if ($newChild) { + newChildren.push($newChild); } } if (fragment) { insertBefore(currentEl, fragment, fragmentNextSibling); } + + return newChildren; } // 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, currentEl: HTMLElement) { - const currentChildren = $current.children; - const newChildren = $new.children; + const newKeys = new Set( + $new.children.map(($newChild) => { + const key = 'props' in $newChild ? $newChild.props.key : undefined; - const newKeys = new Set(); - for (const $newChild of newChildren) { - const key = 'props' in $newChild ? $newChild.props.key : undefined; + 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 && 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`'); + } } - if ($newChild.type === VirtualType.Fragment) { - throw new Error('[Teact] Fragment can not be child of container with `teactFastList`'); - } - } - - newKeys.add(key); - } + return key; + }), + ); // Build a collection of old children that also remain in the new list let currentRemainingIndex = 0; - const remainingByKey: Record = {}; - for (let i = 0, l = currentChildren.length; i < l; i++) { - const $currentChild = currentChildren[i]; + const remainingByKey = $current.children + .reduce((acc, $currentChild, i) => { + let key = 'props' in $currentChild ? $currentChild.props.key : undefined; + // eslint-disable-next-line no-null/no-null + const isKeyPresent = key !== undefined && key !== null; - 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 - if (isKeyPresent && !newKeys.has(key)) { - renderWithVirtual(currentEl, $currentChild, undefined, $new, -1); - - 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 { + // First we process removed children + if (isKeyPresent && !newKeys.has(key)) { renderWithVirtual(currentEl, $currentChild, undefined, $new, -1); - continue; + return acc; + } else if (!isKeyPresent) { + const $newChild = $new.children[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, -1); + + return acc; + } } - } - // Then we build up info about remaining children - remainingByKey[key] = { - $element: $currentChild, - index: currentRemainingIndex++, - orderKey: 'props' in $currentChild ? $currentChild.props.teactOrderKey : undefined, - }; - } + // Then we build up info about remaining children + acc[key] = { + $element: $currentChild, + index: currentRemainingIndex++, + orderKey: 'props' in $currentChild ? $currentChild.props.teactOrderKey : undefined, + }; + return acc; + }, {} as Record); + let newChildren: VirtualElement[] = []; + + let fragmentElements: VirtualElement[] | undefined; let fragmentIndex: number | undefined; - let fragmentSize: number | undefined; let currentPreservedIndex = 0; - for (let i = 0, l = newChildren.length; i < l; i++) { - const $newChild = newChildren[i]; + $new.children.forEach(($newChild, i) => { const key = 'props' in $newChild ? $newChild.props.key : `${INDEX_KEY_PREFIX}${i}`; const currentChildInfo = remainingByKey[key]; if (!currentChildInfo) { - if (fragmentSize === undefined) { + if (!fragmentElements) { + fragmentElements = []; fragmentIndex = i; - fragmentSize = 0; } - fragmentSize++; - continue; + fragmentElements.push($newChild); + return; } // This prepends new children to the top - if (fragmentSize) { - renderFragment(fragmentIndex!, fragmentSize, currentEl, $new); - fragmentSize = undefined; + if (fragmentElements) { + newChildren = newChildren.concat(renderFragment(fragmentElements, fragmentIndex!, currentEl, $new)); + fragmentElements = undefined; fragmentIndex = undefined; } @@ -552,44 +521,34 @@ function renderFastListChildren($current: VirtualElementParent, $new: VirtualEle const nextSibling = currentEl.childNodes[isMovingDown ? i + 1 : i]; const options = shouldMoveNode ? (nextSibling ? { nextSibling } : { forceMoveToEnd: true }) : undefined; - const $renderedChild = renderWithVirtual(currentEl, currentChildInfo.$element, $newChild, $new, i, options); - if ($renderedChild !== $newChild) { - newChildren[i] = $renderedChild; - } - } + newChildren.push(renderWithVirtual(currentEl, currentChildInfo.$element, $newChild, $new, i, options)); + }); // This appends new children to the bottom - if (fragmentSize) { - renderFragment(fragmentIndex!, fragmentSize, currentEl, $new); + if (fragmentElements) { + newChildren = newChildren.concat(renderFragment(fragmentElements, fragmentIndex!, currentEl, $new)); } + + return newChildren; } function renderFragment( - fragmentIndex: number, fragmentSize: number, parentEl: HTMLElement, $parent: VirtualElementParent, + elements: VirtualElement[], fragmentIndex: number, parentEl: HTMLElement, $parent: VirtualElementParent, ) { const nextSibling = parentEl.childNodes[fragmentIndex]; - if (fragmentSize === 1) { - const $child = $parent.children[fragmentIndex]; - const $renderedChild = renderWithVirtual(parentEl, undefined, $child, $parent, fragmentIndex, { nextSibling }); - if ($renderedChild !== $child) { - $parent.children[fragmentIndex] = $renderedChild; - } - - return; + if (elements.length === 1) { + return [renderWithVirtual(parentEl, undefined, elements[0], $parent, fragmentIndex, { nextSibling })]; } const fragment = document.createDocumentFragment(); - - for (let i = fragmentIndex; i < fragmentIndex + fragmentSize; i++) { - const $child = $parent.children[i]; - const $renderedChild = renderWithVirtual(parentEl, undefined, $child, $parent, i, { fragment }); - if ($renderedChild !== $child) { - $parent.children[i] = $renderedChild; - } - } + const newChildren = elements.map(($element, i) => ( + renderWithVirtual(parentEl, undefined, $element, $parent, fragmentIndex + i, { fragment }) + )); insertBefore(parentEl, fragment, nextSibling); + + return newChildren; } function setElementRef($element: VirtualElementTag, htmlElement: HTMLElement | undefined) { @@ -620,7 +579,7 @@ function processControlled(tag: string, props: AnyLiteral) { } = props; props.onChange = undefined; - props.onInput = (e: ChangeEvent) => { + props.onInput = (e: React.ChangeEvent) => { onInput?.(e); onChange?.(e); @@ -665,7 +624,7 @@ function updateAttributes($current: VirtualElementTag, $new: VirtualElementTag, const currentEntries = Object.entries($current.props); const newEntries = Object.entries($new.props); - for (const [key, currentValue] of currentEntries) { + currentEntries.forEach(([key, currentValue]) => { const newValue = $new.props[key]; if ( @@ -677,15 +636,15 @@ function updateAttributes($current: VirtualElementTag, $new: VirtualElementTag, ) { removeAttribute(element, key, currentValue); } - } + }); - for (const [key, newValue] of newEntries) { + newEntries.forEach(([key, newValue]) => { const currentValue = $current.props[key]; if (newValue !== undefined && newValue !== currentValue) { setAttribute(element, key, newValue); } - } + }); } function setAttribute(element: HTMLElement, key: string, value: any) { @@ -768,9 +727,9 @@ export function addExtraClass(element: Element, className: string, forceSingle = if (!forceSingle) { const classNames = className.split(' '); if (classNames.length > 1) { - for (const cn of classNames) { + classNames.forEach((cn) => { addExtraClass(element, cn, true); - } + }); return; } @@ -790,9 +749,9 @@ export function removeExtraClass(element: Element, className: string, forceSingl if (!forceSingle) { const classNames = className.split(' '); if (classNames.length > 1) { - for (const cn of classNames) { + classNames.forEach((cn) => { removeExtraClass(element, cn, true); - } + }); return; } @@ -814,9 +773,9 @@ export function toggleExtraClass(element: Element, className: string, force?: bo if (!forceSingle) { const classNames = className.split(' '); if (classNames.length > 1) { - for (const cn of classNames) { + classNames.forEach((cn) => { toggleExtraClass(element, cn, force, true); - } + }); return; } diff --git a/src/lib/teact/teact.ts b/src/lib/teact/teact.ts index b65269259..6c3ac85bd 100644 --- a/src/lib/teact/teact.ts +++ b/src/lib/teact/teact.ts @@ -16,7 +16,7 @@ export type FC_withDebug = FC & { DEBUG_contentComponentName?: string }; -export enum VirtualType { +export enum VirtualElementTypesEnum { Empty, Text, Tag, @@ -25,18 +25,18 @@ export enum VirtualType { } interface VirtualElementEmpty { - type: VirtualType.Empty; + type: VirtualElementTypesEnum.Empty; target?: Node; } interface VirtualElementText { - type: VirtualType.Text; + type: VirtualElementTypesEnum.Text; target?: Node; value: string; } export interface VirtualElementTag { - type: VirtualType.Tag; + type: VirtualElementTypesEnum.Tag; target?: HTMLElement; tag: string; props: Props; @@ -44,14 +44,14 @@ export interface VirtualElementTag { } export interface VirtualElementComponent { - type: VirtualType.Component; + type: VirtualElementTypesEnum.Component; componentInstance: ComponentInstance; props: Props; children: VirtualElementChildren; } export interface VirtualElementFragment { - type: VirtualType.Fragment; + type: VirtualElementTypesEnum.Fragment; children: VirtualElementChildren; } @@ -71,8 +71,8 @@ interface ComponentInstance { props: Props; renderedValue?: any; mountState: MountState; - hooks?: { - state?: { + hooks: { + state: { cursor: number; byCursor: { value: any; @@ -80,7 +80,7 @@ interface ComponentInstance { setter: StateHookSetter; }[]; }; - effects?: { + effects: { cursor: number; byCursor: { dependencies?: readonly any[]; @@ -89,14 +89,14 @@ interface ComponentInstance { releaseSignals?: NoneToVoidFunction; }[]; }; - memos?: { + memos: { cursor: number; byCursor: { value: any; dependencies: any[]; }[]; }; - refs?: { + refs: { cursor: number; byCursor: { current: any; @@ -141,12 +141,28 @@ const DEBUG_SILENT_RENDERS_FOR = new Set(['TeactMemoWrapper', 'TeactNContainer', let lastComponentId = 0; let renderingInstance: ComponentInstance; +export function isEmptyElement($element: VirtualElement): $element is VirtualElementEmpty { + return $element.type === VirtualElementTypesEnum.Empty; +} + +export function isTextElement($element: VirtualElement): $element is VirtualElementText { + return $element.type === VirtualElementTypesEnum.Text; +} + +export function isTagElement($element: VirtualElement): $element is VirtualElementTag { + return $element.type === VirtualElementTypesEnum.Tag; +} + +export function isComponentElement($element: VirtualElement): $element is VirtualElementComponent { + 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 ( - $element.type === VirtualType.Tag - || $element.type === VirtualType.Component - || $element.type === VirtualType.Fragment - ); + return isTagElement($element) || isComponentElement($element) || isFragmentElement($element); } function createElement( @@ -165,7 +181,7 @@ function createElement( function buildFragmentElement(children: any[]): VirtualElementFragment { return { - type: VirtualType.Fragment, + type: VirtualElementTypesEnum.Fragment, children: buildChildren(children, true), }; } @@ -177,11 +193,29 @@ function createComponentInstance(Component: FC, props: Props, children: any[]): const componentInstance: ComponentInstance = { id: ++lastComponentId, - $element: undefined as unknown as VirtualElementComponent, + $element: {} as VirtualElementComponent, Component, name: Component.name, props, mountState: MountState.New, + hooks: { + state: { + cursor: 0, + byCursor: [], + }, + effects: { + cursor: 0, + byCursor: [], + }, + memos: { + cursor: 0, + byCursor: [], + }, + refs: { + cursor: 0, + byCursor: [], + }, + }, }; componentInstance.$element = buildComponentElement(componentInstance); @@ -194,7 +228,7 @@ function buildComponentElement( children?: VirtualElementChildren, ): VirtualElementComponent { return { - type: VirtualType.Component, + type: VirtualElementTypesEnum.Component, componentInstance, props: componentInstance.props, children: children ? buildChildren(children, true) : [], @@ -203,7 +237,7 @@ function buildComponentElement( function buildTagElement(tag: string, props: Props, children: any[]): VirtualElementTag { return { - type: VirtualType.Tag, + type: VirtualElementTypesEnum.Tag, tag, props, children: buildChildren(children), @@ -253,12 +287,12 @@ function isEmptyPlaceholder(child: any) { function buildChildElement(child: any): VirtualElement { if (isEmptyPlaceholder(child)) { - return { type: VirtualType.Empty }; + return { type: VirtualElementTypesEnum.Empty }; } else if (isParentElement(child)) { return child; } else { return { - type: VirtualType.Text, + type: VirtualElementTypesEnum.Text, value: String(child), }; } @@ -374,20 +408,10 @@ export function renderComponent(componentInstance: ComponentInstance) { safeExec(() => { renderingInstance = componentInstance; - if (componentInstance.hooks) { - if (componentInstance.hooks.state) { - componentInstance.hooks.state.cursor = 0; - } - if (componentInstance.hooks.effects) { - componentInstance.hooks.effects.cursor = 0; - } - if (componentInstance.hooks.memos) { - componentInstance.hooks.memos.cursor = 0; - } - if (componentInstance.hooks.refs) { - componentInstance.hooks.refs.cursor = 0; - } - } + componentInstance.hooks.state.cursor = 0; + componentInstance.hooks.effects.cursor = 0; + componentInstance.hooks.memos.cursor = 0; + componentInstance.hooks.refs.cursor = 0; // eslint-disable-next-line @typescript-eslint/naming-convention let DEBUG_startAt: number | undefined; @@ -445,12 +469,7 @@ export function renderComponent(componentInstance: ComponentInstance) { componentInstance.renderedValue = newRenderedValue; const children = Array.isArray(newRenderedValue) ? newRenderedValue : [newRenderedValue]; - - if (componentInstance.mountState === MountState.New) { - componentInstance.$element.children = buildChildren(children, true); - } else { - componentInstance.$element = buildComponentElement(componentInstance, children); - } + componentInstance.$element = buildComponentElement(componentInstance, children); return componentInstance.$element; } @@ -460,11 +479,11 @@ export function hasElementChanged($old: VirtualElement, $new: VirtualElement) { return true; } else if ($old.type !== $new.type) { return true; - } else if ($old.type === VirtualType.Text && $new.type === VirtualType.Text) { + } else if (isTextElement($old) && isTextElement($new)) { return $old.value !== $new.value; - } else if ($old.type === VirtualType.Tag && $new.type === VirtualType.Tag) { + } else if (isTagElement($old) && isTagElement($new)) { return ($old.tag !== $new.tag) || ($old.props.key !== $new.props.key); - } else if ($old.type === VirtualType.Component && $new.type === VirtualType.Component) { + } else if (isComponentElement($old) && isComponentElement($new)) { return ( $old.componentInstance.Component !== $new.componentInstance.Component ) || ( @@ -488,16 +507,14 @@ export function unmountComponent(componentInstance: ComponentInstance) { idsToExcludeFromUpdate.add(componentInstance.id); - if (componentInstance.hooks?.effects) { - for (const effect of componentInstance.hooks.effects.byCursor) { - if (effect.cleanup) { - safeExec(effect.cleanup); - } - - effect.cleanup = undefined; - effect.releaseSignals?.(); + componentInstance.hooks.effects.byCursor.forEach((effect) => { + if (effect.cleanup) { + safeExec(effect.cleanup); } - } + + effect.cleanup = undefined; + effect.releaseSignals?.(); + }); componentInstance.mountState = MountState.Unmounted; @@ -506,39 +523,27 @@ export function unmountComponent(componentInstance: ComponentInstance) { // We need to remove all references to DOM objects. We also clean all other references, just in case function helpGc(componentInstance: ComponentInstance) { - const { - effects, state, memos, refs, - } = componentInstance.hooks || {}; + componentInstance.hooks.effects.byCursor.forEach((hook) => { + hook.schedule = undefined as any; + hook.cleanup = undefined as any; + hook.releaseSignals = undefined as any; + hook.dependencies = undefined; + }); - if (effects) { - for (const hook of effects.byCursor) { - hook.schedule = undefined as any; - hook.cleanup = undefined as any; - hook.releaseSignals = undefined as any; - hook.dependencies = undefined; - } - } + componentInstance.hooks.state.byCursor.forEach((hook) => { + hook.value = undefined; + hook.nextValue = undefined; + hook.setter = undefined as any; + }); - if (state) { - for (const hook of state.byCursor) { - hook.value = undefined; - hook.nextValue = undefined; - hook.setter = undefined as any; - } - } + componentInstance.hooks.memos.byCursor.forEach((hook) => { + hook.value = undefined as any; + hook.dependencies = undefined as any; + }); - if (memos) { - for (const hook of memos.byCursor) { - hook.value = undefined as any; - hook.dependencies = undefined as any; - } - } - - if (refs) { - for (const hook of refs.byCursor) { - hook.current = undefined as any; - } - } + componentInstance.hooks.refs.byCursor.forEach((hook) => { + hook.current = undefined as any; + }); componentInstance.hooks = undefined as any; componentInstance.$element = undefined as any; @@ -553,11 +558,9 @@ function prepareComponentForFrame(componentInstance: ComponentInstance) { return; } - if (componentInstance.hooks?.state) { - for (const hook of componentInstance.hooks.state.byCursor) { - hook.value = hook.nextValue; - } - } + componentInstance.hooks.state.byCursor.forEach((hook) => { + hook.value = hook.nextValue; + }); } function forceUpdateComponent(componentInstance: ComponentInstance) { @@ -577,13 +580,6 @@ function forceUpdateComponent(componentInstance: ComponentInstance) { export function useState(): [T | undefined, StateHookSetter]; export function useState(initial: T, debugKey?: string): [T, StateHookSetter]; export function useState(initial?: T, debugKey?: string): [T, StateHookSetter] { - if (!renderingInstance.hooks) { - renderingInstance.hooks = {}; - } - if (!renderingInstance.hooks.state) { - renderingInstance.hooks.state = { cursor: 0, byCursor: [] }; - } - const { cursor, byCursor } = renderingInstance.hooks.state; const componentInstance = renderingInstance; @@ -636,13 +632,6 @@ function useEffectBase( dependencies?: readonly any[], debugKey?: string, ) { - if (!renderingInstance.hooks) { - renderingInstance.hooks = {}; - } - if (!renderingInstance.hooks.effects) { - renderingInstance.hooks.effects = { cursor: 0, byCursor: [] }; - } - const { cursor, byCursor } = renderingInstance.hooks.effects; const componentInstance = renderingInstance; @@ -722,7 +711,7 @@ function useEffectBase( if (dependencies && byCursor[cursor]?.dependencies) { if (dependencies.some((dependency, i) => dependency !== byCursor[cursor].dependencies![i])) { - if (DEBUG && debugKey) { + if (debugKey) { const causedBy = dependencies.reduce((res, newValue, i) => { const prevValue = byCursor[cursor].dependencies![i]; if (newValue !== prevValue) { @@ -770,9 +759,7 @@ function useEffectBase( } return () => { - for (const cleanup of cleanups) { - cleanup(); - } + cleanups.forEach((cleanup) => cleanup()); }; } @@ -797,13 +784,6 @@ export function useMemo( debugKey?: string, debugHitRateKey?: string, ): T { - if (!renderingInstance.hooks) { - renderingInstance.hooks = {}; - } - if (!renderingInstance.hooks.memos) { - renderingInstance.hooks.memos = { cursor: 0, byCursor: [] }; - } - const { cursor, byCursor } = renderingInstance.hooks.memos; let { value } = byCursor[cursor] || {}; @@ -881,13 +861,6 @@ export function useRef(): { current: T | undefined }; // TT way (empty is `un export function useRef(initial: null): { current: T | null }; // React way (empty is `null`) // eslint-disable-next-line no-null/no-null export function useRef(initial?: T | null) { - if (!renderingInstance.hooks) { - renderingInstance.hooks = {}; - } - if (!renderingInstance.hooks.refs) { - renderingInstance.hooks.refs = { cursor: 0, byCursor: [] }; - } - const { cursor, byCursor } = renderingInstance.hooks.refs; if (!byCursor[cursor]) { byCursor[cursor] = {