[Perf] Teact: Avoid scheduling redundant effect cleanups, refactoring
This commit is contained in:
parent
2f0eaf72df
commit
f503841d36
@ -655,43 +655,123 @@ function useEffectBase(
|
|||||||
if (!renderingInstance.hooks) {
|
if (!renderingInstance.hooks) {
|
||||||
renderingInstance.hooks = {};
|
renderingInstance.hooks = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!renderingInstance.hooks.effects) {
|
if (!renderingInstance.hooks.effects) {
|
||||||
renderingInstance.hooks.effects = { cursor: 0, byCursor: [] };
|
renderingInstance.hooks.effects = { cursor: 0, byCursor: [] };
|
||||||
}
|
}
|
||||||
|
|
||||||
const { cursor, byCursor } = renderingInstance.hooks.effects;
|
const { cursor, byCursor } = renderingInstance.hooks.effects;
|
||||||
|
const effectConfig = byCursor[cursor];
|
||||||
const componentInstance = renderingInstance;
|
const componentInstance = renderingInstance;
|
||||||
|
|
||||||
const runEffectCleanup = () => safeExec(() => {
|
function schedule() {
|
||||||
const { cleanup } = byCursor[cursor];
|
scheduleEffect(componentInstance, cursor, effect, isLayout);
|
||||||
if (!cleanup) {
|
}
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
if (dependencies && effectConfig?.dependencies) {
|
||||||
let DEBUG_startAt: number | undefined;
|
if (dependencies.some((dependency, i) => dependency !== effectConfig.dependencies![i])) {
|
||||||
if (DEBUG) {
|
if (DEBUG && debugKey) {
|
||||||
DEBUG_startAt = performance.now();
|
const causedBy = dependencies.reduce((res, newValue, i) => {
|
||||||
}
|
const prevValue = effectConfig.dependencies![i];
|
||||||
|
if (newValue !== prevValue) {
|
||||||
|
res.push(`${i}: ${prevValue} => ${newValue}`);
|
||||||
|
}
|
||||||
|
|
||||||
cleanup();
|
return res;
|
||||||
|
}, []);
|
||||||
|
|
||||||
if (DEBUG) {
|
|
||||||
const duration = performance.now() - DEBUG_startAt!;
|
|
||||||
const componentName = DEBUG_resolveComponentName(componentInstance.Component);
|
|
||||||
if (duration > DEBUG_EFFECT_THRESHOLD) {
|
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console.warn(
|
console.log(`[Teact] Effect "${debugKey}" caused by dependencies.`, causedBy.join(', '));
|
||||||
`[Teact] Slow cleanup at effect cursor #${cursor}: ${componentName}, ${Math.round(duration)} ms`,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
schedule();
|
||||||
}
|
}
|
||||||
}, () => {
|
} else {
|
||||||
// eslint-disable-next-line no-console, max-len
|
if (debugKey) {
|
||||||
console.error(`[Teact] Error in effect cleanup at cursor #${cursor} in ${componentInstance.name}`, componentInstance);
|
// eslint-disable-next-line no-console
|
||||||
}, () => {
|
console.log(`[Teact] Effect "${debugKey}" caused by missing dependencies.`);
|
||||||
byCursor[cursor].cleanup = undefined;
|
}
|
||||||
});
|
|
||||||
|
schedule();
|
||||||
|
}
|
||||||
|
|
||||||
|
function setupSignals() {
|
||||||
|
const cleanups = dependencies?.filter(isSignal).map((signal, i) => signal.subscribe(() => {
|
||||||
|
if (debugKey) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(`[Teact] Effect "${debugKey}" caused by signal #${i} new value:`, signal());
|
||||||
|
}
|
||||||
|
|
||||||
|
byCursor[cursor].schedule!();
|
||||||
|
}));
|
||||||
|
|
||||||
|
if (!cleanups?.length) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
for (const cleanup of cleanups) {
|
||||||
|
cleanup();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
byCursor[cursor] = {
|
||||||
|
...effectConfig,
|
||||||
|
dependencies,
|
||||||
|
schedule,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!effectConfig) {
|
||||||
|
byCursor[cursor].releaseSignals = setupSignals();
|
||||||
|
}
|
||||||
|
|
||||||
|
renderingInstance.hooks.effects.cursor++;
|
||||||
|
}
|
||||||
|
|
||||||
|
function scheduleEffect(
|
||||||
|
componentInstance: ComponentInstance,
|
||||||
|
cursor: number,
|
||||||
|
effect: Effect,
|
||||||
|
isLayout: boolean,
|
||||||
|
) {
|
||||||
|
const { byCursor } = componentInstance.hooks!.effects!;
|
||||||
|
const cleanup = byCursor[cursor]?.cleanup;
|
||||||
|
const cleanupsContainer = isLayout ? pendingLayoutCleanups : pendingCleanups;
|
||||||
|
const effectsContainer = isLayout ? pendingLayoutEffects : pendingEffects;
|
||||||
|
const effectId = `${componentInstance.id}_${cursor}`;
|
||||||
|
|
||||||
|
if (cleanup) {
|
||||||
|
const runEffectCleanup = () => safeExec(() => {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||||
|
let DEBUG_startAt: number | undefined;
|
||||||
|
if (DEBUG) {
|
||||||
|
DEBUG_startAt = performance.now();
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup();
|
||||||
|
|
||||||
|
if (DEBUG) {
|
||||||
|
const duration = performance.now() - DEBUG_startAt!;
|
||||||
|
const componentName = DEBUG_resolveComponentName(componentInstance.Component);
|
||||||
|
if (duration > DEBUG_EFFECT_THRESHOLD) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.warn(
|
||||||
|
`[Teact] Slow cleanup at effect cursor #${cursor}: ${componentName}, ${Math.round(duration)} ms`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}, () => {
|
||||||
|
// eslint-disable-next-line no-console, max-len
|
||||||
|
console.error(`[Teact] Error in effect cleanup at cursor #${cursor} in ${componentInstance.name}`, componentInstance);
|
||||||
|
}, () => {
|
||||||
|
byCursor[cursor].cleanup = undefined;
|
||||||
|
});
|
||||||
|
|
||||||
|
cleanupsContainer.set(effectId, runEffectCleanup);
|
||||||
|
}
|
||||||
|
|
||||||
const runEffect = () => safeExec(() => {
|
const runEffect = () => safeExec(() => {
|
||||||
if (componentInstance.mountState === MountState.Unmounted) {
|
if (componentInstance.mountState === MountState.Unmounted) {
|
||||||
@ -722,81 +802,9 @@ function useEffectBase(
|
|||||||
console.error(`[Teact] Error in effect at cursor #${cursor} in ${componentInstance.name}`, componentInstance);
|
console.error(`[Teact] Error in effect at cursor #${cursor} in ${componentInstance.name}`, componentInstance);
|
||||||
});
|
});
|
||||||
|
|
||||||
function schedule() {
|
effectsContainer.set(effectId, runEffect);
|
||||||
const effectId = `${componentInstance.id}_${cursor}`;
|
|
||||||
|
|
||||||
if (isLayout) {
|
runUpdatePassOnRaf();
|
||||||
pendingLayoutCleanups.set(effectId, runEffectCleanup);
|
|
||||||
pendingLayoutEffects.set(effectId, runEffect);
|
|
||||||
} else {
|
|
||||||
pendingCleanups.set(effectId, runEffectCleanup);
|
|
||||||
pendingEffects.set(effectId, runEffect);
|
|
||||||
}
|
|
||||||
|
|
||||||
runUpdatePassOnRaf();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (dependencies && byCursor[cursor]?.dependencies) {
|
|
||||||
if (dependencies.some((dependency, i) => dependency !== byCursor[cursor].dependencies![i])) {
|
|
||||||
if (DEBUG && debugKey) {
|
|
||||||
const causedBy = dependencies.reduce((res, newValue, i) => {
|
|
||||||
const prevValue = byCursor[cursor].dependencies![i];
|
|
||||||
if (newValue !== prevValue) {
|
|
||||||
res.push(`${i}: ${prevValue} => ${newValue}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// eslint-disable-next-line no-console
|
|
||||||
console.log(`[Teact] Effect "${debugKey}" caused by dependencies.`, causedBy.join(', '));
|
|
||||||
}
|
|
||||||
|
|
||||||
schedule();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (debugKey) {
|
|
||||||
// eslint-disable-next-line no-console
|
|
||||||
console.log(`[Teact] Effect "${debugKey}" caused by missing dependencies.`);
|
|
||||||
}
|
|
||||||
|
|
||||||
schedule();
|
|
||||||
}
|
|
||||||
|
|
||||||
const isFirstRun = !byCursor[cursor];
|
|
||||||
|
|
||||||
byCursor[cursor] = {
|
|
||||||
...byCursor[cursor],
|
|
||||||
dependencies,
|
|
||||||
schedule,
|
|
||||||
};
|
|
||||||
|
|
||||||
function setupSignals() {
|
|
||||||
const cleanups = dependencies?.filter(isSignal).map((signal, i) => signal.subscribe(() => {
|
|
||||||
if (debugKey) {
|
|
||||||
// eslint-disable-next-line no-console
|
|
||||||
console.log(`[Teact] Effect "${debugKey}" caused by signal #${i} new value:`, signal());
|
|
||||||
}
|
|
||||||
|
|
||||||
byCursor[cursor].schedule!();
|
|
||||||
}));
|
|
||||||
|
|
||||||
if (!cleanups?.length) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
for (const cleanup of cleanups) {
|
|
||||||
cleanup();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isFirstRun) {
|
|
||||||
byCursor[cursor].releaseSignals = setupSignals();
|
|
||||||
}
|
|
||||||
|
|
||||||
renderingInstance.hooks.effects.cursor++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useEffect(effect: Effect, dependencies?: readonly any[], debugKey?: string) {
|
export function useEffect(effect: Effect, dependencies?: readonly any[], debugKey?: string) {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user