Teact: Align effect order with other libraries (#6685)
This commit is contained in:
parent
846ec883d4
commit
1f0ac5b276
@ -24,7 +24,7 @@ import usePrevious from '../hooks/usePrevious';
|
|||||||
import { useSignalEffect } from '../hooks/useSignalEffect';
|
import { useSignalEffect } from '../hooks/useSignalEffect';
|
||||||
import { getIsInBackground } from '../hooks/window/useBackgroundMode';
|
import { getIsInBackground } from '../hooks/window/useBackgroundMode';
|
||||||
|
|
||||||
// import Test from './test/TestSvg';
|
// import Test from './test/TestCleanupOrder';
|
||||||
import Auth from './auth/Auth';
|
import Auth from './auth/Auth';
|
||||||
import Notifications from './common/Notifications';
|
import Notifications from './common/Notifications';
|
||||||
import UiLoader from './common/UiLoader';
|
import UiLoader from './common/UiLoader';
|
||||||
|
|||||||
@ -1,53 +1,84 @@
|
|||||||
import { useEffect, useLayoutEffect, useState } from '../../lib/teact/teact';
|
/* eslint-disable no-console */
|
||||||
|
import { type TeactNode, useEffect, useLayoutEffect, useState } from '../../lib/teact/teact';
|
||||||
const TestCleanupOrder = () => {
|
|
||||||
const [, setRand] = useState(Math.random());
|
|
||||||
|
|
||||||
|
function Component1({ children }: { children?: TeactNode }) {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// eslint-disable-next-line no-console
|
console.log('Effect 1');
|
||||||
console.log('effect 1');
|
return () => console.log('Cleanup 1');
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
setRand(Math.random());
|
|
||||||
}, 3000);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
// eslint-disable-next-line no-console
|
|
||||||
console.log('cleanup 1');
|
|
||||||
};
|
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// eslint-disable-next-line no-console
|
console.log('Effect 1.2');
|
||||||
console.log('effect 2');
|
return () => console.log('Cleanup 1.2');
|
||||||
|
|
||||||
return () => {
|
|
||||||
// eslint-disable-next-line no-console
|
|
||||||
console.log('cleanup 2');
|
|
||||||
};
|
|
||||||
});
|
});
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
// eslint-disable-next-line no-console
|
console.log('Layout 1');
|
||||||
console.log('layout effect 1');
|
return () => console.log('Layout Cleanup 1');
|
||||||
|
|
||||||
return () => {
|
|
||||||
// eslint-disable-next-line no-console
|
|
||||||
console.log('layout cleanup 1');
|
|
||||||
};
|
|
||||||
});
|
});
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
// eslint-disable-next-line no-console
|
console.log('Layout 1.2');
|
||||||
console.log('layout effect 2');
|
return () => console.log('Layout Cleanup 1.2');
|
||||||
|
});
|
||||||
|
return children;
|
||||||
|
}
|
||||||
|
|
||||||
return () => {
|
function Component1B({ children }: { children?: TeactNode }) {
|
||||||
// eslint-disable-next-line no-console
|
useEffect(() => {
|
||||||
console.log('layout cleanup 2');
|
console.log('Effect 1b');
|
||||||
};
|
return () => console.log('Cleanup 1b');
|
||||||
});
|
});
|
||||||
|
|
||||||
return <div>Test</div>;
|
useLayoutEffect(() => {
|
||||||
};
|
console.log('Layout 1b');
|
||||||
|
return () => console.log('Layout Cleanup 1b');
|
||||||
|
});
|
||||||
|
return 'B';
|
||||||
|
}
|
||||||
|
|
||||||
|
function Component2({ children }: { children?: TeactNode }) {
|
||||||
|
useEffect(() => {
|
||||||
|
console.log('Effect 2');
|
||||||
|
return () => console.log('Cleanup 2');
|
||||||
|
});
|
||||||
|
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
console.log('Layout 2');
|
||||||
|
return () => console.log('Layout Cleanup 2');
|
||||||
|
});
|
||||||
|
return children;
|
||||||
|
}
|
||||||
|
|
||||||
|
function Component3() {
|
||||||
|
useEffect(() => {
|
||||||
|
console.log('Effect 3');
|
||||||
|
return () => console.log('Cleanup 3');
|
||||||
|
});
|
||||||
|
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
console.log('Layout 3');
|
||||||
|
return () => console.log('Layout Cleanup 3');
|
||||||
|
});
|
||||||
|
return <div>Leaf</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
function TestCleanupOrder() {
|
||||||
|
const [isMounted, setIsMounted] = useState(true);
|
||||||
|
return (
|
||||||
|
<div style="padding: 1rem" onClick={() => setIsMounted((p) => !p)}>
|
||||||
|
{isMounted && (
|
||||||
|
<>
|
||||||
|
<Component1>
|
||||||
|
<Component2>
|
||||||
|
<Component3 />
|
||||||
|
</Component2>
|
||||||
|
</Component1>
|
||||||
|
<Component1B />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export default TestCleanupOrder;
|
export default TestCleanupOrder;
|
||||||
|
|||||||
@ -155,6 +155,7 @@ const Fragment = Symbol('Fragment') as unknown as FC<{ children: TeactNode }>;
|
|||||||
const DEBUG_RENDER_THRESHOLD = 7;
|
const DEBUG_RENDER_THRESHOLD = 7;
|
||||||
const DEBUG_EFFECT_THRESHOLD = 7;
|
const DEBUG_EFFECT_THRESHOLD = 7;
|
||||||
const DEBUG_SILENT_RENDERS_FOR = new Set(['TeactMemoWrapper', 'TeactNContainer', 'Button', 'ListItem', 'MenuItem']);
|
const DEBUG_SILENT_RENDERS_FOR = new Set(['TeactMemoWrapper', 'TeactNContainer', 'Button', 'ListItem', 'MenuItem']);
|
||||||
|
const MAX_EFFECT_CURSORS_PER_INSTANCE = 10000;
|
||||||
|
|
||||||
let contextCounter = 0;
|
let contextCounter = 0;
|
||||||
|
|
||||||
@ -315,12 +316,37 @@ if (DEBUG) {
|
|||||||
|
|
||||||
let instancesPendingUpdate = new Set<ComponentInstance>();
|
let instancesPendingUpdate = new Set<ComponentInstance>();
|
||||||
let idsToExcludeFromUpdate = new Set<number>();
|
let idsToExcludeFromUpdate = new Set<number>();
|
||||||
let pendingEffects = new Map<string, Effect>();
|
let pendingEffects = new Map<number, Effect>();
|
||||||
let pendingCleanups = new Map<string, EffectCleanup>();
|
let pendingCleanups = new Map<number, EffectCleanup>();
|
||||||
let pendingLayoutEffects = new Map<string, Effect>();
|
let pendingLayoutEffects = new Map<number, Effect>();
|
||||||
let pendingLayoutCleanups = new Map<string, EffectCleanup>();
|
let pendingLayoutCleanups = new Map<number, EffectCleanup>();
|
||||||
let areImmediateEffectsCaptured = false;
|
let areImmediateEffectsCaptured = false;
|
||||||
|
|
||||||
|
/*
|
||||||
|
Effect call order:
|
||||||
|
- Regular call order inside component
|
||||||
|
- Child to parent
|
||||||
|
- Parent to child on unmount
|
||||||
|
- Sibling behavior is not defined
|
||||||
|
*/
|
||||||
|
function runEffectsMap<T extends NoneToVoidFunction>(map: Map<number, T>) {
|
||||||
|
Array.from(map.entries())
|
||||||
|
.sort(([aKey], [bKey]) => {
|
||||||
|
const idA = Math.floor(aKey / MAX_EFFECT_CURSORS_PER_INSTANCE);
|
||||||
|
const idB = Math.floor(bKey / MAX_EFFECT_CURSORS_PER_INSTANCE);
|
||||||
|
|
||||||
|
const idDiff = idB - idA;
|
||||||
|
if (idDiff !== 0) {
|
||||||
|
return idDiff;
|
||||||
|
}
|
||||||
|
|
||||||
|
const cursorA = aKey % MAX_EFFECT_CURSORS_PER_INSTANCE;
|
||||||
|
const cursorB = bKey % MAX_EFFECT_CURSORS_PER_INSTANCE;
|
||||||
|
|
||||||
|
return cursorA - cursorB;
|
||||||
|
}).forEach(([_, cb]) => void cb());
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Order:
|
Order:
|
||||||
- component effect cleanups
|
- component effect cleanups
|
||||||
@ -350,11 +376,11 @@ const runUpdatePassOnRaf = throttleWith(requestMeasure, () => {
|
|||||||
|
|
||||||
const currentCleanups = pendingCleanups;
|
const currentCleanups = pendingCleanups;
|
||||||
pendingCleanups = new Map();
|
pendingCleanups = new Map();
|
||||||
currentCleanups.forEach((cb) => cb());
|
runEffectsMap(currentCleanups);
|
||||||
|
|
||||||
const currentEffects = pendingEffects;
|
const currentEffects = pendingEffects;
|
||||||
pendingEffects = new Map();
|
pendingEffects = new Map();
|
||||||
currentEffects.forEach((cb) => cb());
|
runEffectsMap(currentEffects);
|
||||||
|
|
||||||
requestMutation(() => {
|
requestMutation(() => {
|
||||||
instancesToUpdate.forEach(prepareComponentForFrame);
|
instancesToUpdate.forEach(prepareComponentForFrame);
|
||||||
@ -382,11 +408,11 @@ export function captureImmediateEffects() {
|
|||||||
function runCapturedImmediateEffects() {
|
function runCapturedImmediateEffects() {
|
||||||
const currentLayoutCleanups = pendingLayoutCleanups;
|
const currentLayoutCleanups = pendingLayoutCleanups;
|
||||||
pendingLayoutCleanups = new Map();
|
pendingLayoutCleanups = new Map();
|
||||||
currentLayoutCleanups.forEach((cb) => cb());
|
runEffectsMap(currentLayoutCleanups);
|
||||||
|
|
||||||
const currentLayoutEffects = pendingLayoutEffects;
|
const currentLayoutEffects = pendingLayoutEffects;
|
||||||
pendingLayoutEffects = new Map();
|
pendingLayoutEffects = new Map();
|
||||||
currentLayoutEffects.forEach((cb) => cb());
|
runEffectsMap(currentLayoutEffects);
|
||||||
|
|
||||||
areImmediateEffectsCaptured = false;
|
areImmediateEffectsCaptured = false;
|
||||||
}
|
}
|
||||||
@ -738,6 +764,11 @@ function useEffectBase(
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderingInstance.hooks.effects.cursor++;
|
renderingInstance.hooks.effects.cursor++;
|
||||||
|
|
||||||
|
if (DEBUG && cursor >= MAX_EFFECT_CURSORS_PER_INSTANCE) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.warn(`[Teact] Effect cursor #${cursor} in ${componentInstance.name} is out of bounds. How?`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function scheduleEffect(
|
function scheduleEffect(
|
||||||
@ -750,7 +781,7 @@ function scheduleEffect(
|
|||||||
const cleanup = byCursor[cursor]?.cleanup;
|
const cleanup = byCursor[cursor]?.cleanup;
|
||||||
const cleanupsContainer = isLayout ? pendingLayoutCleanups : pendingCleanups;
|
const cleanupsContainer = isLayout ? pendingLayoutCleanups : pendingCleanups;
|
||||||
const effectsContainer = isLayout ? pendingLayoutEffects : pendingEffects;
|
const effectsContainer = isLayout ? pendingLayoutEffects : pendingEffects;
|
||||||
const effectId = `${componentInstance.id}_${cursor}`;
|
const effectId = componentInstance.id * MAX_EFFECT_CURSORS_PER_INSTANCE + cursor;
|
||||||
|
|
||||||
if (cleanup) {
|
if (cleanup) {
|
||||||
const runEffectCleanup = () => safeExec(() => {
|
const runEffectCleanup = () => safeExec(() => {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user