[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) {
|
||||
renderingInstance.hooks = {};
|
||||
}
|
||||
|
||||
if (!renderingInstance.hooks.effects) {
|
||||
renderingInstance.hooks.effects = { cursor: 0, byCursor: [] };
|
||||
}
|
||||
|
||||
const { cursor, byCursor } = renderingInstance.hooks.effects;
|
||||
const effectConfig = byCursor[cursor];
|
||||
const componentInstance = renderingInstance;
|
||||
|
||||
const runEffectCleanup = () => safeExec(() => {
|
||||
const { cleanup } = byCursor[cursor];
|
||||
if (!cleanup) {
|
||||
return;
|
||||
}
|
||||
function schedule() {
|
||||
scheduleEffect(componentInstance, cursor, effect, isLayout);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
let DEBUG_startAt: number | undefined;
|
||||
if (DEBUG) {
|
||||
DEBUG_startAt = performance.now();
|
||||
}
|
||||
if (dependencies && effectConfig?.dependencies) {
|
||||
if (dependencies.some((dependency, i) => dependency !== effectConfig.dependencies![i])) {
|
||||
if (DEBUG && debugKey) {
|
||||
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
|
||||
console.warn(
|
||||
`[Teact] Slow cleanup at effect cursor #${cursor}: ${componentName}, ${Math.round(duration)} ms`,
|
||||
);
|
||||
console.log(`[Teact] Effect "${debugKey}" caused by dependencies.`, causedBy.join(', '));
|
||||
}
|
||||
|
||||
schedule();
|
||||
}
|
||||
}, () => {
|
||||
// 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;
|
||||
});
|
||||
} else {
|
||||
if (debugKey) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(`[Teact] Effect "${debugKey}" caused by missing 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();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
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(() => {
|
||||
if (componentInstance.mountState === MountState.Unmounted) {
|
||||
@ -722,81 +802,9 @@ function useEffectBase(
|
||||
console.error(`[Teact] Error in effect at cursor #${cursor} in ${componentInstance.name}`, componentInstance);
|
||||
});
|
||||
|
||||
function schedule() {
|
||||
const effectId = `${componentInstance.id}_${cursor}`;
|
||||
effectsContainer.set(effectId, runEffect);
|
||||
|
||||
if (isLayout) {
|
||||
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++;
|
||||
runUpdatePassOnRaf();
|
||||
}
|
||||
|
||||
export function useEffect(effect: Effect, dependencies?: readonly any[], debugKey?: string) {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user