[Perf] Teact: Introduce useUnmountCleanup to reduce redundant effects

This commit is contained in:
Alexander Zinchuk 2024-09-19 20:43:33 +02:00
parent 1c40b9b514
commit 2f0eaf72df
10 changed files with 69 additions and 62 deletions

View File

@ -2,7 +2,11 @@ import type { RefObject } from 'react';
import type { FC } from '../../lib/teact/teact';
import React, {
getIsHeavyAnimating,
memo, useEffect, useRef, useState,
memo,
useEffect,
useRef,
useState,
useUnmountCleanup,
} from '../../lib/teact/teact';
import type RLottieInstance from '../../lib/rlottie/RLottie';
@ -118,11 +122,9 @@ const AnimatedSticker: FC<OwnProps> = ({
}, [color, shouldUseColorFilter]);
const isUnmountedRef = useRef(false);
useEffect(() => {
return () => {
isUnmountedRef.current = true;
};
}, []);
useUnmountCleanup(() => {
isUnmountedRef.current = true;
});
const init = useLastCallback(() => {
if (
@ -184,11 +186,9 @@ const AnimatedSticker: FC<OwnProps> = ({
animation.setColor(rgbColor.current);
}, [color, animation]);
useEffect(() => {
return () => {
animationRef.current?.removeView(viewId);
};
}, [viewId]);
useUnmountCleanup(() => {
animationRef.current?.removeView(viewId);
});
const playAnimation = useLastCallback((shouldRestart = false) => {
if (

View File

@ -1,14 +1,13 @@
import { useEffect } from '../../../lib/teact/teact';
import { useUnmountCleanup } from '../../../lib/teact/teact';
import { createSignal } from '../../../util/signals';
export const [getIsVideoWaiting, setIsVideoWaiting] = createSignal(false);
export default function useVideoWaitingSignal() {
useEffect(() => {
return () => {
setIsVideoWaiting(false);
};
}, []);
useUnmountCleanup(() => {
setIsVideoWaiting(false);
});
return [getIsVideoWaiting, setIsVideoWaiting] as const;
}

View File

@ -1,15 +1,13 @@
import { useEffect } from '../../../lib/teact/teact';
import { useUnmountCleanup } from '../../../lib/teact/teact';
import { createSignal } from '../../../util/signals';
const [getZoomChange, setZoomChange] = createSignal(1);
export default function useZoomChange() {
useEffect(() => {
return () => {
setZoomChange(1);
};
}, []);
useUnmountCleanup(() => {
setZoomChange(1);
});
return [getZoomChange, setZoomChange] as const;
}

View File

@ -5,7 +5,7 @@ import type { GlobalState } from '../../global/types';
import type { Signal, SignalSetter } from '../../util/signals';
import { createSignal } from '../../util/signals';
import useEffectOnce from '../useEffectOnce';
import useSyncEffect from '../useSyncEffect';
/*
This hook is a more performant variation of the standard React `useSelector` hook. It allows to:
@ -31,24 +31,25 @@ addCallback((global: GlobalState) => {
function useSelectorSignal<T extends unknown>(selector: Selector<T>): Signal<T> {
let state = bySelector.get(selector);
if (!state) {
const [getter, setter] = createSignal(selector(getGlobal()));
state = { clientsCount: 0, getter, setter };
bySelector.set(selector, state);
}
useEffectOnce(() => {
state!.clientsCount++;
useSyncEffect(() => {
const state2 = bySelector.get(selector)!;
state2.clientsCount++;
return () => {
state!.clientsCount--;
state2.clientsCount--;
if (!state!.clientsCount) {
if (!state2.clientsCount) {
bySelector.delete(selector);
}
};
});
}, [selector]);
return state.getter as Signal<T>;
}

View File

@ -1,14 +1,13 @@
import { useEffect } from '../lib/teact/teact';
import { useUnmountCleanup } from '../lib/teact/teact';
import { createSignal } from '../util/signals';
export const [getCurrentTime, setCurrentTime] = createSignal(0);
export default function useCurrentTimeSignal() {
useEffect(() => {
return () => {
setCurrentTime(0);
};
}, []);
useUnmountCleanup(() => {
setCurrentTime(0);
});
return [getCurrentTime, setCurrentTime] as const;
}

View File

@ -1,4 +1,4 @@
import { useCallback, useEffect, useRef } from '../lib/teact/teact';
import { useCallback, useRef, useUnmountCleanup } from '../lib/teact/teact';
const DEFAULT_THRESHOLD = 250;
@ -41,11 +41,9 @@ function useLongPress({
window.clearTimeout(timerId.current);
}, [onEnd, onClick]);
useEffect(() => {
return () => {
window.clearTimeout(timerId.current);
};
}, []);
useUnmountCleanup(() => {
window.clearTimeout(timerId.current);
});
return {
onMouseDown: start,

View File

@ -1,7 +1,6 @@
import { useRef } from '../lib/teact/teact';
import { useRef, useUnmountCleanup } from '../lib/teact/teact';
import { cleanupEffect, isSignal } from '../util/signals';
import useEffectOnce from './useEffectOnce';
export function useSignalEffect(effect: NoneToVoidFunction, dependencies: readonly any[]) {
// The is extracted from `useEffectOnce` to run before all effects
@ -16,9 +15,7 @@ export function useSignalEffect(effect: NoneToVoidFunction, dependencies: readon
});
}
useEffectOnce(() => {
return () => {
cleanupEffect(effect);
};
useUnmountCleanup(() => {
cleanupEffect(effect);
});
}

View File

@ -1,6 +1,5 @@
import { useRef } from '../lib/teact/teact';
import { useRef, useUnmountCleanup } from '../lib/teact/teact';
import useEffectOnce from './useEffectOnce';
import usePreviousDeprecated from './usePreviousDeprecated';
export default function useSyncEffect<const T extends readonly any[]>(
@ -15,9 +14,7 @@ export default function useSyncEffect<const T extends readonly any[]>(
cleanupRef.current = effect(prevDeps || []) ?? undefined;
}
useEffectOnce(() => {
return () => {
cleanupRef.current?.();
};
useUnmountCleanup(() => {
cleanupRef.current?.();
});
}

View File

@ -92,7 +92,7 @@ interface ComponentInstance {
cursor: number;
byCursor: {
dependencies?: readonly any[];
schedule: NoneToVoidFunction;
schedule?: NoneToVoidFunction;
cleanup?: NoneToVoidFunction;
releaseSignals?: NoneToVoidFunction;
}[];
@ -778,7 +778,7 @@ function useEffectBase(
console.log(`[Teact] Effect "${debugKey}" caused by signal #${i} new value:`, signal());
}
byCursor[cursor].schedule();
byCursor[cursor].schedule!();
}));
if (!cleanups?.length) {
@ -807,6 +807,26 @@ export function useLayoutEffect(effect: Effect, dependencies?: readonly any[], d
return useEffectBase(true, effect, dependencies, debugKey);
}
export function useUnmountCleanup(cleanup: NoneToVoidFunction) {
if (!renderingInstance.hooks) {
renderingInstance.hooks = {};
}
if (!renderingInstance.hooks.effects) {
renderingInstance.hooks.effects = { cursor: 0, byCursor: [] };
}
const { cursor, byCursor } = renderingInstance.hooks.effects;
if (!byCursor[cursor]) {
byCursor[cursor] = {
cleanup,
};
}
renderingInstance.hooks.effects.cursor++;
}
export function useMemo<T extends any>(
resolver: () => T,
dependencies: any[],

View File

@ -6,7 +6,7 @@ import { handleError } from '../../util/handleError';
import { orderBy } from '../../util/iteratees';
import { throttleWithTickEnd } from '../../util/schedulers';
import { requestMeasure } from '../fasterdom/fasterdom';
import React, { DEBUG_resolveComponentName, getIsHeavyAnimating, useEffect } from './teact';
import React, { DEBUG_resolveComponentName, getIsHeavyAnimating, useUnmountCleanup } from './teact';
import useForceUpdate from '../../hooks/useForceUpdate';
import useUniqueId from '../../hooks/useUniqueId';
@ -255,11 +255,9 @@ export function withGlobal<OwnProps extends AnyLiteral>(
const id = useUniqueId();
const forceUpdate = useForceUpdate();
useEffect(() => {
return () => {
containers.delete(id);
};
}, [id]);
useUnmountCleanup(() => {
containers.delete(id);
});
let container = containers.get(id)!;
if (!container) {