Teact: Always run effects before component renders
This commit is contained in:
parent
deeda69f77
commit
6b235729f4
910
package-lock.json
generated
910
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -1,11 +1,6 @@
|
|||||||
import type { ReactElement } from 'react';
|
import type { ReactElement } from 'react';
|
||||||
import { DEBUG, DEBUG_MORE } from '../../config';
|
import { DEBUG, DEBUG_MORE } from '../../config';
|
||||||
import {
|
import { throttleWithRafFallback } from '../../util/schedulers';
|
||||||
throttleWithRafFallback,
|
|
||||||
throttleWithPrimaryRafFallback,
|
|
||||||
throttleWithTickEnd,
|
|
||||||
throttleWithPrimaryTickEnd,
|
|
||||||
} from '../../util/schedulers';
|
|
||||||
import { orderBy } from '../../util/iteratees';
|
import { orderBy } from '../../util/iteratees';
|
||||||
import { getUnequalProps } from '../../util/arePropsShallowEqual';
|
import { getUnequalProps } from '../../util/arePropsShallowEqual';
|
||||||
import { handleError } from '../../util/handleError';
|
import { handleError } from '../../util/handleError';
|
||||||
@ -127,6 +122,9 @@ export type TeactNode =
|
|||||||
| boolean
|
| boolean
|
||||||
| TeactNode[];
|
| TeactNode[];
|
||||||
|
|
||||||
|
type Effect = () => (NoneToVoidFunction | void);
|
||||||
|
type EffectCleanup = NoneToVoidFunction;
|
||||||
|
|
||||||
const Fragment = Symbol('Fragment');
|
const Fragment = Symbol('Fragment');
|
||||||
|
|
||||||
const DEBUG_RENDER_THRESHOLD = 7;
|
const DEBUG_RENDER_THRESHOLD = 7;
|
||||||
@ -306,6 +304,10 @@ document.addEventListener('dblclick', () => {
|
|||||||
|
|
||||||
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 pendingCleanups = new Map<string, EffectCleanup>();
|
||||||
|
let pendingLayoutEffects = new Map<string, Effect>();
|
||||||
|
let pendingLayoutCleanups = new Map<string, EffectCleanup>();
|
||||||
|
|
||||||
const runUpdatePassOnRaf = throttleWithRafFallback(() => {
|
const runUpdatePassOnRaf = throttleWithRafFallback(() => {
|
||||||
idsToExcludeFromUpdate = new Set();
|
idsToExcludeFromUpdate = new Set();
|
||||||
@ -316,9 +318,15 @@ const runUpdatePassOnRaf = throttleWithRafFallback(() => {
|
|||||||
|
|
||||||
instancesPendingUpdate = new Set();
|
instancesPendingUpdate = new Set();
|
||||||
|
|
||||||
instancesToUpdate.forEach((instance) => {
|
const currentCleanups = pendingCleanups;
|
||||||
prepareComponentForFrame(instance);
|
pendingCleanups = new Map();
|
||||||
});
|
currentCleanups.forEach((cb) => cb());
|
||||||
|
|
||||||
|
const currentEffects = pendingEffects;
|
||||||
|
pendingEffects = new Map();
|
||||||
|
currentEffects.forEach((cb) => cb());
|
||||||
|
|
||||||
|
instancesToUpdate.forEach(prepareComponentForFrame);
|
||||||
|
|
||||||
instancesToUpdate.forEach((instance) => {
|
instancesToUpdate.forEach((instance) => {
|
||||||
if (idsToExcludeFromUpdate!.has(instance.id)) {
|
if (idsToExcludeFromUpdate!.has(instance.id)) {
|
||||||
@ -327,6 +335,14 @@ const runUpdatePassOnRaf = throttleWithRafFallback(() => {
|
|||||||
|
|
||||||
forceUpdateComponent(instance);
|
forceUpdateComponent(instance);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const currentLayoutCleanups = pendingLayoutCleanups;
|
||||||
|
pendingLayoutCleanups = new Map();
|
||||||
|
currentLayoutCleanups.forEach((cb) => cb());
|
||||||
|
|
||||||
|
const currentLayoutEffects = pendingLayoutEffects;
|
||||||
|
pendingLayoutEffects = new Map();
|
||||||
|
currentLayoutEffects.forEach((cb) => cb());
|
||||||
});
|
});
|
||||||
|
|
||||||
function scheduleUpdate(componentInstance: ComponentInstance) {
|
function scheduleUpdate(componentInstance: ComponentInstance) {
|
||||||
@ -561,9 +577,8 @@ export function useState<T>(initial?: T, debugKey?: string): [T, StateHookSetter
|
|||||||
}
|
}
|
||||||
|
|
||||||
function useEffectBase(
|
function useEffectBase(
|
||||||
schedulerFn: (cb: NoneToVoidFunction) => void,
|
isLayout: boolean,
|
||||||
primarySchedulerFn: (cb: NoneToVoidFunction) => void,
|
effect: Effect,
|
||||||
effect: () => NoneToVoidFunction | void,
|
|
||||||
dependencies?: readonly any[],
|
dependencies?: readonly any[],
|
||||||
debugKey?: string,
|
debugKey?: string,
|
||||||
) {
|
) {
|
||||||
@ -643,8 +658,16 @@ function useEffectBase(
|
|||||||
}
|
}
|
||||||
|
|
||||||
function schedule() {
|
function schedule() {
|
||||||
primarySchedulerFn(execCleanup);
|
const effectId = `${componentInstance.id}_${cursor}`;
|
||||||
schedulerFn(exec);
|
|
||||||
|
if (isLayout) {
|
||||||
|
pendingLayoutCleanups.set(effectId, execCleanup);
|
||||||
|
pendingLayoutEffects.set(effectId, exec);
|
||||||
|
} else {
|
||||||
|
pendingCleanups.set(effectId, execCleanup);
|
||||||
|
pendingEffects.set(effectId, exec);
|
||||||
|
runUpdatePassOnRaf();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dependencies && byCursor[cursor]?.dependencies) {
|
if (dependencies && byCursor[cursor]?.dependencies) {
|
||||||
@ -703,26 +726,12 @@ function useEffectBase(
|
|||||||
renderingInstance.hooks.effects.cursor++;
|
renderingInstance.hooks.effects.cursor++;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useEffect(
|
export function useEffect(effect: Effect, dependencies?: readonly any[], debugKey?: string) {
|
||||||
effect: () => NoneToVoidFunction | void,
|
return useEffectBase(false, effect, dependencies, debugKey);
|
||||||
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(
|
export function useLayoutEffect(effect: Effect, dependencies?: readonly any[], debugKey?: string) {
|
||||||
effect: () => NoneToVoidFunction | void,
|
return useEffectBase(true, effect, dependencies, debugKey);
|
||||||
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 {
|
||||||
|
|||||||
@ -142,7 +142,8 @@ let timeout: NodeJS.Timeout | undefined;
|
|||||||
|
|
||||||
const FAST_RAF_TIMEOUT_FALLBACK_MS = 300;
|
const FAST_RAF_TIMEOUT_FALLBACK_MS = 300;
|
||||||
|
|
||||||
// May result in an immediate execution if called from another `requestAnimationFrame` callback
|
// May result in an immediate execution if called from another RAF callback which was scheduled
|
||||||
|
// (and therefore is executed) earlier than RAF callback scheduled by `fastRaf`
|
||||||
export function fastRaf(callback: NoneToVoidFunction, isPrimary = false, withTimeoutFallback = false) {
|
export function fastRaf(callback: NoneToVoidFunction, isPrimary = false, withTimeoutFallback = false) {
|
||||||
if (!fastRafCallbacks) {
|
if (!fastRafCallbacks) {
|
||||||
fastRafCallbacks = !withTimeoutFallback && !isPrimary ? [callback] : [];
|
fastRafCallbacks = !withTimeoutFallback && !isPrimary ? [callback] : [];
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user