Teact: Avoid multiple effects in a single frame
This commit is contained in:
parent
31e52956c2
commit
d40a2a6240
@ -1,11 +1,10 @@
|
|||||||
import type { ReactElement } from 'react';
|
import type { ReactElement } from 'react';
|
||||||
import { DEBUG, DEBUG_MORE } from '../../config';
|
import { DEBUG, DEBUG_MORE } from '../../config';
|
||||||
import {
|
import {
|
||||||
fastRafWithFallback,
|
|
||||||
fastRafPrimaryWithFallback,
|
|
||||||
onTickEnd,
|
|
||||||
onTickEndPrimary,
|
|
||||||
throttleWithRafFallback,
|
throttleWithRafFallback,
|
||||||
|
throttleWithPrimaryRafFallback,
|
||||||
|
throttleWithTickEnd,
|
||||||
|
throttleWithPrimaryTickEnd,
|
||||||
} from '../../util/schedulers';
|
} from '../../util/schedulers';
|
||||||
import { orderBy } from '../../util/iteratees';
|
import { orderBy } from '../../util/iteratees';
|
||||||
import { getUnequalProps } from '../../util/arePropsShallowEqual';
|
import { getUnequalProps } from '../../util/arePropsShallowEqual';
|
||||||
@ -15,7 +14,9 @@ import { incrementOverlayCounter } from '../../util/debugOverlay';
|
|||||||
export type Props = AnyLiteral;
|
export type Props = AnyLiteral;
|
||||||
export type FC<P extends Props = any> = (props: P) => any;
|
export type FC<P extends Props = any> = (props: P) => any;
|
||||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||||
export type FC_withDebug = FC & { DEBUG_contentComponentName?: string };
|
export type FC_withDebug =
|
||||||
|
FC
|
||||||
|
& { DEBUG_contentComponentName?: string };
|
||||||
|
|
||||||
export enum VirtualElementTypesEnum {
|
export enum VirtualElementTypesEnum {
|
||||||
Empty,
|
Empty,
|
||||||
@ -79,9 +80,8 @@ interface ComponentInstance {
|
|||||||
effects: {
|
effects: {
|
||||||
cursor: number;
|
cursor: number;
|
||||||
byCursor: {
|
byCursor: {
|
||||||
effect: () => void;
|
|
||||||
dependencies?: readonly any[];
|
dependencies?: readonly any[];
|
||||||
cleanup?: Function;
|
cleanup?: NoneToVoidFunction;
|
||||||
}[];
|
}[];
|
||||||
};
|
};
|
||||||
memos: {
|
memos: {
|
||||||
@ -455,8 +455,7 @@ export function unmountComponent(componentInstance: ComponentInstance) {
|
|||||||
// We need to remove all references to DOM objects. We also clean all other references, just in case
|
// We need to remove all references to DOM objects. We also clean all other references, just in case
|
||||||
function helpGc(componentInstance: ComponentInstance) {
|
function helpGc(componentInstance: ComponentInstance) {
|
||||||
componentInstance.hooks.effects.byCursor.forEach((hook) => {
|
componentInstance.hooks.effects.byCursor.forEach((hook) => {
|
||||||
hook.cleanup = undefined;
|
hook.cleanup = undefined as any;
|
||||||
hook.effect = undefined as any;
|
|
||||||
hook.dependencies = undefined;
|
hook.dependencies = undefined;
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -554,10 +553,10 @@ export function useState<T>(initial?: T, debugKey?: string): [T, StateHookSetter
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
function useLayoutEffectBase(
|
function useEffectBase(
|
||||||
schedulerFn: typeof onTickEnd | typeof requestAnimationFrame,
|
schedulerFn: (cb: NoneToVoidFunction) => void,
|
||||||
primarySchedulerFn: typeof onTickEnd | typeof requestAnimationFrame,
|
primarySchedulerFn: (cb: NoneToVoidFunction) => void,
|
||||||
effect: () => Function | void,
|
effect: () => NoneToVoidFunction | void,
|
||||||
dependencies?: readonly any[],
|
dependencies?: readonly any[],
|
||||||
debugKey?: string,
|
debugKey?: string,
|
||||||
) {
|
) {
|
||||||
@ -636,7 +635,12 @@ function useLayoutEffectBase(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (byCursor[cursor] !== undefined && dependencies && byCursor[cursor].dependencies) {
|
function schedule() {
|
||||||
|
primarySchedulerFn(execCleanup);
|
||||||
|
schedulerFn(exec);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dependencies && byCursor[cursor]?.dependencies) {
|
||||||
if (dependencies.some((dependency, i) => dependency !== byCursor[cursor].dependencies![i])) {
|
if (dependencies.some((dependency, i) => dependency !== byCursor[cursor].dependencies![i])) {
|
||||||
if (debugKey) {
|
if (debugKey) {
|
||||||
const causedBy = dependencies.reduce((res, newValue, i) => {
|
const causedBy = dependencies.reduce((res, newValue, i) => {
|
||||||
@ -652,8 +656,7 @@ function useLayoutEffectBase(
|
|||||||
console.log(`[Teact] Effect "${debugKey}" caused by dependencies.`, causedBy.join(', '));
|
console.log(`[Teact] Effect "${debugKey}" caused by dependencies.`, causedBy.join(', '));
|
||||||
}
|
}
|
||||||
|
|
||||||
primarySchedulerFn(execCleanup);
|
schedule();
|
||||||
schedulerFn(exec);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (debugKey) {
|
if (debugKey) {
|
||||||
@ -661,25 +664,37 @@ function useLayoutEffectBase(
|
|||||||
console.log(`[Teact] Effect "${debugKey}" caused by missing dependencies.`);
|
console.log(`[Teact] Effect "${debugKey}" caused by missing dependencies.`);
|
||||||
}
|
}
|
||||||
|
|
||||||
primarySchedulerFn(execCleanup);
|
schedule();
|
||||||
schedulerFn(exec);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
byCursor[cursor] = {
|
byCursor[cursor] = {
|
||||||
effect,
|
...byCursor[cursor],
|
||||||
dependencies,
|
dependencies,
|
||||||
cleanup: byCursor[cursor]?.cleanup,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
renderingInstance.hooks.effects.cursor++;
|
renderingInstance.hooks.effects.cursor++;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useEffect(effect: () => Function | void, dependencies?: readonly any[], debugKey?: string) {
|
export function useEffect(
|
||||||
return useLayoutEffectBase(fastRafWithFallback, fastRafPrimaryWithFallback, effect, dependencies, debugKey);
|
effect: () => NoneToVoidFunction | void,
|
||||||
|
dependencies?: readonly any[],
|
||||||
|
debugKey?: string,
|
||||||
|
) {
|
||||||
|
const schedulerFn = useMemo(() => throttleWithRafFallback((cb: NoneToVoidFunction) => cb()), []);
|
||||||
|
const primarySchedulerFn = useMemo(() => throttleWithPrimaryRafFallback((cb: NoneToVoidFunction) => cb()), []);
|
||||||
|
|
||||||
|
return useEffectBase(schedulerFn, primarySchedulerFn, effect, dependencies, debugKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useLayoutEffect(effect: () => Function | void, dependencies?: readonly any[], debugKey?: string) {
|
export function useLayoutEffect(
|
||||||
return useLayoutEffectBase(onTickEnd, onTickEndPrimary, effect, dependencies, debugKey);
|
effect: () => NoneToVoidFunction | void,
|
||||||
|
dependencies?: readonly any[],
|
||||||
|
debugKey?: string,
|
||||||
|
) {
|
||||||
|
const schedulerFn = useMemo(() => throttleWithTickEnd((cb: NoneToVoidFunction) => cb()), []);
|
||||||
|
const primarySchedulerFn = useMemo(() => throttleWithPrimaryTickEnd((cb: NoneToVoidFunction) => cb()), []);
|
||||||
|
|
||||||
|
return useEffectBase(schedulerFn, primarySchedulerFn, effect, dependencies, debugKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useMemo<T extends any>(resolver: () => T, dependencies: any[], debugKey?: string): T {
|
export function useMemo<T extends any>(resolver: () => T, dependencies: any[], debugKey?: string): T {
|
||||||
|
|||||||
@ -92,6 +92,10 @@ export function throttleWithTickEnd<F extends AnyToVoidFunction>(fn: F) {
|
|||||||
return throttleWith(onTickEnd, fn);
|
return throttleWith(onTickEnd, fn);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function throttleWithPrimaryTickEnd<F extends AnyToVoidFunction>(fn: F) {
|
||||||
|
return throttleWith(onTickEndPrimary, fn);
|
||||||
|
}
|
||||||
|
|
||||||
export function throttleWith<F extends AnyToVoidFunction>(schedulerFn: Scheduler, fn: F) {
|
export function throttleWith<F extends AnyToVoidFunction>(schedulerFn: Scheduler, fn: F) {
|
||||||
let waiting = false;
|
let waiting = false;
|
||||||
let args: Parameters<F>;
|
let args: Parameters<F>;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user