[Perf] useSharedIntersectionObserver: Consider heavy animation

This commit is contained in:
Alexander Zinchuk 2024-09-19 20:43:44 +02:00
parent 7634094883
commit 94a9f7eba8
2 changed files with 36 additions and 9 deletions

View File

@ -1,9 +1,9 @@
import { useEffect } from '../lib/teact/teact'; import { getIsHeavyAnimating, useEffect } from '../lib/teact/teact';
import type { CallbackManager } from '../util/callbacks'; import type { CallbackManager } from '../util/callbacks';
import { createCallbackManager } from '../util/callbacks'; import { createCallbackManager } from '../util/callbacks';
import { useStateRef } from './useStateRef'; import useLastCallback from './useLastCallback';
const elementObserverMap = new Map<HTMLElement, [IntersectionObserver, CallbackManager]>(); const elementObserverMap = new Map<HTMLElement, [IntersectionObserver, CallbackManager]>();
@ -12,7 +12,7 @@ export default function useSharedIntersectionObserver(
onIntersectionChange: (entry: IntersectionObserverEntry) => void, onIntersectionChange: (entry: IntersectionObserverEntry) => void,
isDisabled = false, isDisabled = false,
) { ) {
const onIntersectionChangeRef = useStateRef(onIntersectionChange); const onIntersectionChangeLast = useLastCallback(onIntersectionChange);
useEffect(() => { useEffect(() => {
const el = refOrElement && 'current' in refOrElement ? refOrElement.current : refOrElement; const el = refOrElement && 'current' in refOrElement ? refOrElement.current : refOrElement;
@ -20,13 +20,29 @@ export default function useSharedIntersectionObserver(
return undefined; return undefined;
} }
const callback: IntersectionObserverCallback = ([entry]) => { const entriesAccumulator = new Map<Element, IntersectionObserverEntry>();
// Ignore updates when element is not properly mounted (`display: none`)
if (!(entry.target as HTMLElement).offsetWidth || !(entry.target as HTMLElement).offsetHeight) { function flush() {
return; for (const entry of entriesAccumulator.values()) {
// Ignore updates when element is not properly mounted (`display: none`)
if (!(entry.target as HTMLElement).offsetParent) {
continue;
}
onIntersectionChangeLast(entry);
} }
onIntersectionChangeRef.current(entry); entriesAccumulator.clear();
}
const callback: IntersectionObserverCallback = ([entry]) => {
entriesAccumulator.set(entry.target, entry);
if (!getIsHeavyAnimating()) {
flush();
} else {
getIsHeavyAnimating.once(flush);
}
}; };
let [observer, callbackManager] = elementObserverMap.get(el) || [undefined, undefined]; let [observer, callbackManager] = elementObserverMap.get(el) || [undefined, undefined];
@ -46,5 +62,5 @@ export default function useSharedIntersectionObserver(
elementObserverMap.delete(el); elementObserverMap.delete(el);
} }
}; };
}, [isDisabled, onIntersectionChangeRef, refOrElement]); }, [isDisabled, refOrElement]);
} }

View File

@ -12,6 +12,7 @@ const SIGNAL_MARK = Symbol('SIGNAL_MARK');
export type Signal<T = any> = ((() => T) & { export type Signal<T = any> = ((() => T) & {
readonly [SIGNAL_MARK]: symbol; readonly [SIGNAL_MARK]: symbol;
subscribe: (cb: AnyToVoidFunction) => NoneToVoidFunction; subscribe: (cb: AnyToVoidFunction) => NoneToVoidFunction;
once: (cb: AnyToVoidFunction) => NoneToVoidFunction;
}); });
export type SignalSetter = (newValue: any) => void; export type SignalSetter = (newValue: any) => void;
@ -51,6 +52,15 @@ export function createSignal<T>(defaultValue?: T) {
}; };
} }
function once(effect: NoneToVoidFunction) {
const unsub = subscribe(() => {
unsub();
effect();
});
return unsub;
}
function getter() { function getter() {
if (currentEffect) { if (currentEffect) {
subscribe(currentEffect); subscribe(currentEffect);
@ -71,6 +81,7 @@ export function createSignal<T>(defaultValue?: T) {
const signal = Object.assign(getter as Signal<T>, { const signal = Object.assign(getter as Signal<T>, {
[SIGNAL_MARK]: SIGNAL_MARK, [SIGNAL_MARK]: SIGNAL_MARK,
subscribe, subscribe,
once,
}); });
return [signal, setter] as const; return [signal, setter] as const;