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

View File

@ -1,14 +1,13 @@
import { useEffect } from '../../../lib/teact/teact'; import { useUnmountCleanup } from '../../../lib/teact/teact';
import { createSignal } from '../../../util/signals'; import { createSignal } from '../../../util/signals';
export const [getIsVideoWaiting, setIsVideoWaiting] = createSignal(false); export const [getIsVideoWaiting, setIsVideoWaiting] = createSignal(false);
export default function useVideoWaitingSignal() { export default function useVideoWaitingSignal() {
useEffect(() => { useUnmountCleanup(() => {
return () => { setIsVideoWaiting(false);
setIsVideoWaiting(false); });
};
}, []);
return [getIsVideoWaiting, setIsVideoWaiting] as const; 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'; import { createSignal } from '../../../util/signals';
const [getZoomChange, setZoomChange] = createSignal(1); const [getZoomChange, setZoomChange] = createSignal(1);
export default function useZoomChange() { export default function useZoomChange() {
useEffect(() => { useUnmountCleanup(() => {
return () => { setZoomChange(1);
setZoomChange(1); });
};
}, []);
return [getZoomChange, setZoomChange] as const; 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 type { Signal, SignalSetter } from '../../util/signals';
import { createSignal } 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: 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> { function useSelectorSignal<T extends unknown>(selector: Selector<T>): Signal<T> {
let state = bySelector.get(selector); let state = bySelector.get(selector);
if (!state) { if (!state) {
const [getter, setter] = createSignal(selector(getGlobal())); const [getter, setter] = createSignal(selector(getGlobal()));
state = { clientsCount: 0, getter, setter }; state = { clientsCount: 0, getter, setter };
bySelector.set(selector, state); bySelector.set(selector, state);
} }
useEffectOnce(() => { useSyncEffect(() => {
state!.clientsCount++; const state2 = bySelector.get(selector)!;
state2.clientsCount++;
return () => { return () => {
state!.clientsCount--; state2.clientsCount--;
if (!state!.clientsCount) { if (!state2.clientsCount) {
bySelector.delete(selector); bySelector.delete(selector);
} }
}; };
}); }, [selector]);
return state.getter as Signal<T>; 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'; import { createSignal } from '../util/signals';
export const [getCurrentTime, setCurrentTime] = createSignal(0); export const [getCurrentTime, setCurrentTime] = createSignal(0);
export default function useCurrentTimeSignal() { export default function useCurrentTimeSignal() {
useEffect(() => { useUnmountCleanup(() => {
return () => { setCurrentTime(0);
setCurrentTime(0); });
};
}, []);
return [getCurrentTime, setCurrentTime] as const; 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; const DEFAULT_THRESHOLD = 250;
@ -41,11 +41,9 @@ function useLongPress({
window.clearTimeout(timerId.current); window.clearTimeout(timerId.current);
}, [onEnd, onClick]); }, [onEnd, onClick]);
useEffect(() => { useUnmountCleanup(() => {
return () => { window.clearTimeout(timerId.current);
window.clearTimeout(timerId.current); });
};
}, []);
return { return {
onMouseDown: start, 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 { cleanupEffect, isSignal } from '../util/signals';
import useEffectOnce from './useEffectOnce';
export function useSignalEffect(effect: NoneToVoidFunction, dependencies: readonly any[]) { export function useSignalEffect(effect: NoneToVoidFunction, dependencies: readonly any[]) {
// The is extracted from `useEffectOnce` to run before all effects // The is extracted from `useEffectOnce` to run before all effects
@ -16,9 +15,7 @@ export function useSignalEffect(effect: NoneToVoidFunction, dependencies: readon
}); });
} }
useEffectOnce(() => { useUnmountCleanup(() => {
return () => { cleanupEffect(effect);
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'; import usePreviousDeprecated from './usePreviousDeprecated';
export default function useSyncEffect<const T extends readonly any[]>( 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; cleanupRef.current = effect(prevDeps || []) ?? undefined;
} }
useEffectOnce(() => { useUnmountCleanup(() => {
return () => { cleanupRef.current?.();
cleanupRef.current?.();
};
}); });
} }

View File

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

View File

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