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 {
VirtualElement,
VirtualElementComponent,
VirtualElementTag,
VirtualElementParent,
VirtualElementChildren,
VirtualElementReal,
VirtualElementComponent,
VirtualElementFragment,
VirtualElementParent,
VirtualElementReal,
VirtualElementTag,
} from './teact';
import {
captureImmediateEffects,
hasElementChanged,
isComponentElement,
isTagElement,
isParentElement,
isTextElement,
isEmptyElement,
isFragmentElement,
isParentElement,
isTagElement,
isTextElement,
mountComponent,
MountState,
renderComponent,
unmountComponent,
isFragmentElement,
captureImmediateEffects,
} from './teact';
import { DEBUG } from '../../config';
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.
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);
}
@ -211,7 +216,7 @@ function initComponent(
) {
const { componentInstance } = $element;
if (!componentInstance.isMounted) {
if (componentInstance.mountState === MountState.New) {
$element = mountComponent(componentInstance);
setupComponentUpdateListener(parentEl, $element, $parent, index);
@ -219,8 +224,6 @@ function initComponent(
if (isComponentElement($firstChild)) {
$element.children = [initComponent(parentEl, $firstChild, $element, 0)];
}
componentInstance.isMounted = true;
}
return $element;

View File

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