Teact: Disable setting state on unmounted components

This commit is contained in:
Alexander Zinchuk 2023-07-05 13:15:06 +02:00
parent af973d6f20
commit 8cdc1b87be
2 changed files with 35 additions and 22 deletions

View File

@ -1,24 +1,25 @@
import type { import type {
VirtualElement, VirtualElement,
VirtualElementComponent,
VirtualElementTag,
VirtualElementParent,
VirtualElementChildren, VirtualElementChildren,
VirtualElementReal, VirtualElementComponent,
VirtualElementFragment, VirtualElementFragment,
VirtualElementParent,
VirtualElementReal,
VirtualElementTag,
} from './teact'; } from './teact';
import { import {
captureImmediateEffects,
hasElementChanged, hasElementChanged,
isComponentElement, isComponentElement,
isTagElement,
isParentElement,
isTextElement,
isEmptyElement, isEmptyElement,
isFragmentElement,
isParentElement,
isTagElement,
isTextElement,
mountComponent, mountComponent,
MountState,
renderComponent, renderComponent,
unmountComponent, unmountComponent,
isFragmentElement,
captureImmediateEffects,
} from './teact'; } from './teact';
import { DEBUG } from '../../config'; import { DEBUG } from '../../config';
import { addEventListener, removeAllDelegatedListeners, removeEventListener } from './dom-events'; import { addEventListener, removeAllDelegatedListeners, removeEventListener } from './dom-events';
@ -103,7 +104,11 @@ function renderWithVirtual<T extends VirtualElement | undefined>(
} }
// Parent element may have changed, so we need to update the listener closure. // Parent element may have changed, so we need to update the listener closure.
if (!skipComponentUpdate && isNewComponent && ($new as VirtualElementComponent).componentInstance.isMounted) { if (
!skipComponentUpdate
&& isNewComponent
&& ($new as VirtualElementComponent).componentInstance.mountState === MountState.Mounted
) {
setupComponentUpdateListener(parentEl, $new as VirtualElementComponent, $parent, index); setupComponentUpdateListener(parentEl, $new as VirtualElementComponent, $parent, index);
} }
@ -211,7 +216,7 @@ function initComponent(
) { ) {
const { componentInstance } = $element; const { componentInstance } = $element;
if (!componentInstance.isMounted) { if (componentInstance.mountState === MountState.New) {
$element = mountComponent(componentInstance); $element = mountComponent(componentInstance);
setupComponentUpdateListener(parentEl, $element, $parent, index); setupComponentUpdateListener(parentEl, $element, $parent, index);
@ -219,8 +224,6 @@ function initComponent(
if (isComponentElement($firstChild)) { if (isComponentElement($firstChild)) {
$element.children = [initComponent(parentEl, $firstChild, $element, 0)]; $element.children = [initComponent(parentEl, $firstChild, $element, 0)];
} }
componentInstance.isMounted = true;
} }
return $element; return $element;

View File

@ -57,6 +57,12 @@ export interface VirtualElementFragment {
export type StateHookSetter<T> = (newValue: ((current: T) => T) | T) => void; export type StateHookSetter<T> = (newValue: ((current: T) => T) | T) => void;
export enum MountState {
New,
Mounted,
Unmounted,
}
interface ComponentInstance { interface ComponentInstance {
id: number; id: number;
$element: VirtualElementComponent; $element: VirtualElementComponent;
@ -64,7 +70,7 @@ interface ComponentInstance {
name: string; name: string;
props: Props; props: Props;
renderedValue?: any; renderedValue?: any;
isMounted: boolean; mountState: MountState;
hooks: { hooks: {
state: { state: {
cursor: number; cursor: number;
@ -201,7 +207,7 @@ function createComponentInstance(Component: FC, props: Props, children: any[]):
...props, ...props,
...(parsedChildren && { children: parsedChildren }), ...(parsedChildren && { children: parsedChildren }),
}, },
isMounted: false, mountState: MountState.New,
hooks: { hooks: {
state: { state: {
cursor: 0, cursor: 0,
@ -438,7 +444,7 @@ export function renderComponent(componentInstance: ComponentInstance) {
newRenderedValue = componentInstance.renderedValue; newRenderedValue = componentInstance.renderedValue;
}); });
if (componentInstance.isMounted && newRenderedValue === componentInstance.renderedValue) { if (componentInstance.mountState === MountState.Mounted && newRenderedValue === componentInstance.renderedValue) {
return componentInstance.$element; return componentInstance.$element;
} }
@ -472,12 +478,12 @@ export function hasElementChanged($old: VirtualElement, $new: VirtualElement) {
export function mountComponent(componentInstance: ComponentInstance) { export function mountComponent(componentInstance: ComponentInstance) {
renderComponent(componentInstance); renderComponent(componentInstance);
componentInstance.isMounted = true; componentInstance.mountState = MountState.Mounted;
return componentInstance.$element; return componentInstance.$element;
} }
export function unmountComponent(componentInstance: ComponentInstance) { export function unmountComponent(componentInstance: ComponentInstance) {
if (!componentInstance.isMounted) { if (componentInstance.mountState !== MountState.Mounted) {
return; return;
} }
@ -492,7 +498,7 @@ export function unmountComponent(componentInstance: ComponentInstance) {
effect.releaseSignals?.(); effect.releaseSignals?.();
}); });
componentInstance.isMounted = false; componentInstance.mountState = MountState.Unmounted;
helpGc(componentInstance); helpGc(componentInstance);
} }
@ -530,7 +536,7 @@ function helpGc(componentInstance: ComponentInstance) {
} }
function prepareComponentForFrame(componentInstance: ComponentInstance) { function prepareComponentForFrame(componentInstance: ComponentInstance) {
if (!componentInstance.isMounted) { if (componentInstance.mountState !== MountState.Mounted) {
return; return;
} }
@ -540,7 +546,7 @@ function prepareComponentForFrame(componentInstance: ComponentInstance) {
} }
function forceUpdateComponent(componentInstance: ComponentInstance) { function forceUpdateComponent(componentInstance: ComponentInstance) {
if (!componentInstance.isMounted || !componentInstance.onUpdate) { if (componentInstance.mountState !== MountState.Mounted || !componentInstance.onUpdate) {
return; return;
} }
@ -563,6 +569,10 @@ export function useState<T>(initial?: T, debugKey?: string): [T, StateHookSetter
value: initial, value: initial,
nextValue: initial, nextValue: initial,
setter: ((componentInstance) => (newValue: ((current: T) => T) | T) => { setter: ((componentInstance) => (newValue: ((current: T) => T) | T) => {
if (componentInstance.mountState === MountState.Unmounted) {
return;
}
if (typeof newValue === 'function') { if (typeof newValue === 'function') {
newValue = (newValue as (current: T) => T)(byCursor[cursor].nextValue); newValue = (newValue as (current: T) => T)(byCursor[cursor].nextValue);
} }
@ -644,7 +654,7 @@ function useEffectBase(
}); });
const runEffect = () => safeExec(() => { const runEffect = () => safeExec(() => {
if (!componentInstance.isMounted) { if (componentInstance.mountState === MountState.Unmounted) {
return; return;
} }