diff --git a/packages/reactivity/__tests__/computed.spec.ts b/packages/reactivity/__tests__/computed.spec.ts index 0a9f9ecc8d1..6811f1029e5 100644 --- a/packages/reactivity/__tests__/computed.spec.ts +++ b/packages/reactivity/__tests__/computed.spec.ts @@ -2,7 +2,6 @@ import { computed, reactive, effect, - stop, ref, WritableComputedRef, isReadonly @@ -125,7 +124,7 @@ describe('reactivity/computed', () => { expect(dummy).toBe(undefined) value.foo = 1 expect(dummy).toBe(1) - stop(cValue.effect) + cValue.effect.stop() value.foo = 2 expect(dummy).toBe(1) }) @@ -196,7 +195,7 @@ describe('reactivity/computed', () => { it('should expose value when stopped', () => { const x = computed(() => 1) - stop(x.effect) + x.effect.stop() expect(x.value).toBe(1) }) }) diff --git a/packages/reactivity/__tests__/effect.spec.ts b/packages/reactivity/__tests__/effect.spec.ts index e4a8f338c3e..d458a17d840 100644 --- a/packages/reactivity/__tests__/effect.spec.ts +++ b/packages/reactivity/__tests__/effect.spec.ts @@ -494,7 +494,7 @@ describe('reactivity/effect', () => { const runner = effect(() => {}) const otherRunner = effect(runner) expect(runner).not.toBe(otherRunner) - expect(runner.raw).toBe(otherRunner.raw) + expect(runner.effect.fn).toBe(otherRunner.effect.fn) }) it('should not run multiple times for a single mutation', () => { @@ -590,12 +590,13 @@ describe('reactivity/effect', () => { }) it('scheduler', () => { - let runner: any, dummy - const scheduler = jest.fn(_runner => { - runner = _runner + let dummy + let run: any + const scheduler = jest.fn(() => { + run = runner }) const obj = reactive({ foo: 1 }) - effect( + const runner = effect( () => { dummy = obj.foo }, @@ -609,7 +610,7 @@ describe('reactivity/effect', () => { // should not run yet expect(dummy).toBe(1) // manually run - runner() + run() // should have run expect(dummy).toBe(2) }) @@ -633,19 +634,19 @@ describe('reactivity/effect', () => { expect(onTrack).toHaveBeenCalledTimes(3) expect(events).toEqual([ { - effect: runner, + effect: runner.effect, target: toRaw(obj), type: TrackOpTypes.GET, key: 'foo' }, { - effect: runner, + effect: runner.effect, target: toRaw(obj), type: TrackOpTypes.HAS, key: 'bar' }, { - effect: runner, + effect: runner.effect, target: toRaw(obj), type: TrackOpTypes.ITERATE, key: ITERATE_KEY @@ -671,7 +672,7 @@ describe('reactivity/effect', () => { expect(dummy).toBe(2) expect(onTrigger).toHaveBeenCalledTimes(1) expect(events[0]).toEqual({ - effect: runner, + effect: runner.effect, target: toRaw(obj), type: TriggerOpTypes.SET, key: 'foo', @@ -684,7 +685,7 @@ describe('reactivity/effect', () => { expect(dummy).toBeUndefined() expect(onTrigger).toHaveBeenCalledTimes(2) expect(events[1]).toEqual({ - effect: runner, + effect: runner.effect, target: toRaw(obj), type: TriggerOpTypes.DELETE, key: 'foo', diff --git a/packages/reactivity/__tests__/readonly.spec.ts b/packages/reactivity/__tests__/readonly.spec.ts index 80115b2645d..c8bf65b3876 100644 --- a/packages/reactivity/__tests__/readonly.spec.ts +++ b/packages/reactivity/__tests__/readonly.spec.ts @@ -382,7 +382,7 @@ describe('reactivity/readonly', () => { const eff = effect(() => { roArr.includes(2) }) - expect(eff.deps.length).toBe(0) + expect(eff.effect.deps.length).toBe(0) }) test('readonly should track and trigger if wrapping reactive original (collection)', () => { diff --git a/packages/reactivity/src/computed.ts b/packages/reactivity/src/computed.ts index 965ff3eec6b..396e50ecdb0 100644 --- a/packages/reactivity/src/computed.ts +++ b/packages/reactivity/src/computed.ts @@ -1,4 +1,4 @@ -import { effect, ReactiveEffect } from './effect' +import { ReactiveEffect } from './effect' import { Ref, trackRefValue, triggerRefValue } from './ref' import { isFunction, NOOP } from '@vue/shared' import { ReactiveFlags, toRaw } from './reactive' @@ -35,16 +35,12 @@ class ComputedRefImpl { private readonly _setter: ComputedSetter, isReadonly: boolean ) { - this.effect = effect(getter, { - lazy: true, - scheduler: () => { - if (!this._dirty) { - this._dirty = true - triggerRefValue(this) - } + this.effect = new ReactiveEffect(getter, () => { + if (!this._dirty) { + this._dirty = true + triggerRefValue(this) } }) - this[ReactiveFlags.IS_READONLY] = isReadonly } @@ -52,10 +48,10 @@ class ComputedRefImpl { // the computed ref may get wrapped by other proxies e.g. readonly() #3376 const self = toRaw(this) if (self._dirty) { - self._value = this.effect() + self._value = self.effect.run()! self._dirty = false } - trackRefValue(this) + trackRefValue(self) return self._value } diff --git a/packages/reactivity/src/effect.ts b/packages/reactivity/src/effect.ts index df74769e14b..d2733e93cd6 100644 --- a/packages/reactivity/src/effect.ts +++ b/packages/reactivity/src/effect.ts @@ -1,5 +1,5 @@ import { TrackOpTypes, TriggerOpTypes } from './operations' -import { EMPTY_OBJ, extend, isArray, isIntegerKey, isMap } from '@vue/shared' +import { extend, isArray, isIntegerKey, isMap } from '@vue/shared' // The main WeakMap that stores {target -> key -> dep} connections. // Conceptually, it's easier to think of a dependency as a Dep class @@ -9,40 +9,7 @@ type Dep = Set type KeyToDepMap = Map const targetMap = new WeakMap() -export interface ReactiveEffect { - (): T - _isEffect: true - id: number - active: boolean - raw: () => T - deps: Array - options: ReactiveEffectOptions - allowRecurse: boolean -} - -export interface ReactiveEffectOptions { - lazy?: boolean - scheduler?: (job: ReactiveEffect) => void - onTrack?: (event: DebuggerEvent) => void - onTrigger?: (event: DebuggerEvent) => void - onStop?: () => void - /** - * Indicates whether the job is allowed to recursively trigger itself when - * managed by the scheduler. - * - * By default, a job cannot trigger itself because some built-in method calls, - * e.g. Array.prototype.push actually performs reads as well (#1740) which - * can lead to confusing infinite loops. - * The allowed cases are component update functions and watch callbacks. - * Component update functions may update child component props, which in turn - * trigger flush: "pre" watch callbacks that mutates state that the parent - * relies on (#1801). Watch callbacks doesn't track its dependencies so if it - * triggers itself again, it's likely intentional and it is the user's - * responsibility to perform recursive state mutation that eventually - * stabilizes (#1727). - */ - allowRecurse?: boolean -} +export type EffectScheduler = () => void export type DebuggerEvent = { effect: ReactiveEffect @@ -62,52 +29,34 @@ let activeEffect: ReactiveEffect | undefined export const ITERATE_KEY = Symbol(__DEV__ ? 'iterate' : '') export const MAP_KEY_ITERATE_KEY = Symbol(__DEV__ ? 'Map key iterate' : '') +export class ReactiveEffect { + active = true + deps: Dep[] = [] -export function isEffect(fn: any): fn is ReactiveEffect { - return fn && fn._isEffect === true -} - -export function effect( - fn: () => T, - options: ReactiveEffectOptions = EMPTY_OBJ -): ReactiveEffect { - if (isEffect(fn)) { - fn = fn.raw - } - const effect = createReactiveEffect(fn, options) - if (!options.lazy) { - effect() - } - return effect -} - -export function stop(effect: ReactiveEffect) { - if (effect.active) { - cleanup(effect) - if (effect.options.onStop) { - effect.options.onStop() - } - effect.active = false - } -} + // can be attached after creation + onStop?: () => void + // dev only + onTrack?: (event: DebuggerEvent) => void + // dev only + onTrigger?: (event: DebuggerEvent) => void -let uid = 0 + constructor( + public fn: () => T, + public scheduler: EffectScheduler | null = null, + // allow recursive self-invocation + public allowRecurse = false + ) {} -function createReactiveEffect( - fn: () => T, - options: ReactiveEffectOptions -): ReactiveEffect { - const effect = function reactiveEffect(): unknown { - if (!effect.active) { - return fn() + run() { + if (!this.active) { + return this.fn() } - if (!effectStack.includes(effect)) { - cleanup(effect) + if (!effectStack.includes(this)) { + this.cleanup() try { enableTracking() - effectStack.push(effect) - activeEffect = effect - return fn() + effectStack.push((activeEffect = this)) + return this.fn() } finally { effectStack.pop() resetTracking() @@ -115,25 +64,65 @@ function createReactiveEffect( activeEffect = n > 0 ? effectStack[n - 1] : undefined } } - } as ReactiveEffect - effect.id = uid++ - effect.allowRecurse = !!options.allowRecurse - effect._isEffect = true - effect.active = true - effect.raw = fn - effect.deps = [] - effect.options = options - return effect -} + } -function cleanup(effect: ReactiveEffect) { - const { deps } = effect - if (deps.length) { - for (let i = 0; i < deps.length; i++) { - deps[i].delete(effect) + cleanup() { + const { deps } = this + if (deps.length) { + for (let i = 0; i < deps.length; i++) { + deps[i].delete(this) + } + deps.length = 0 } - deps.length = 0 } + + stop() { + if (this.active) { + this.cleanup() + if (this.onStop) { + this.onStop() + } + this.active = false + } + } +} + +export interface ReactiveEffectOptions { + lazy?: boolean + scheduler?: EffectScheduler + allowRecurse?: boolean + onStop?: () => void + onTrack?: (event: DebuggerEvent) => void + onTrigger?: (event: DebuggerEvent) => void +} + +export interface ReactiveEffectRunner { + (): T + effect: ReactiveEffect +} + +export function effect( + fn: () => T, + options?: ReactiveEffectOptions +): ReactiveEffectRunner { + if ((fn as ReactiveEffectRunner).effect) { + fn = (fn as ReactiveEffectRunner).effect.fn + } + + const _effect = new ReactiveEffect(fn) + if (options) { + extend(_effect, options) + } + if (!options || !options.lazy) { + _effect.run() + } + const runner = _effect.run.bind(_effect) as ReactiveEffectRunner + runner.effect = _effect + return runner +} + +export function stop(runner: ReactiveEffectRunner) { + runner.effect.stop() } let shouldTrack = true @@ -185,8 +174,8 @@ export function trackEffects( if (!dep.has(activeEffect!)) { dep.add(activeEffect!) activeEffect!.deps.push(dep) - if (__DEV__ && activeEffect!.options.onTrack) { - activeEffect!.options.onTrack( + if (__DEV__ && activeEffect!.onTrack) { + activeEffect!.onTrack( Object.assign( { effect: activeEffect! @@ -284,13 +273,13 @@ export function triggerEffects( // spread into array for stabilization for (const effect of [...dep]) { if (effect !== activeEffect || effect.allowRecurse) { - if (__DEV__ && effect.options.onTrigger) { - effect.options.onTrigger(extend({ effect }, debuggerEventExtraInfo)) + if (__DEV__ && effect.onTrigger) { + effect.onTrigger(extend({ effect }, debuggerEventExtraInfo)) } - if (effect.options.scheduler) { - effect.options.scheduler(effect) + if (effect.scheduler) { + effect.scheduler() } else { - effect() + effect.run() } } } diff --git a/packages/reactivity/src/index.ts b/packages/reactivity/src/index.ts index 240410141e4..e392f182439 100644 --- a/packages/reactivity/src/index.ts +++ b/packages/reactivity/src/index.ts @@ -46,7 +46,9 @@ export { resetTracking, ITERATE_KEY, ReactiveEffect, + ReactiveEffectRunner, ReactiveEffectOptions, + EffectScheduler, DebuggerEvent } from './effect' export { TrackOpTypes, TriggerOpTypes } from './operations' diff --git a/packages/runtime-core/__tests__/scheduler.spec.ts b/packages/runtime-core/__tests__/scheduler.spec.ts index 5c686334416..92727e99537 100644 --- a/packages/runtime-core/__tests__/scheduler.spec.ts +++ b/packages/runtime-core/__tests__/scheduler.spec.ts @@ -1,4 +1,3 @@ -import { effect, stop } from '@vue/reactivity' import { queueJob, nextTick, @@ -576,20 +575,19 @@ describe('scheduler', () => { // simulate parent component that toggles child const job1 = () => { - stop(job2) + // @ts-ignore + job2.active = false } - job1.id = 0 // need the id to ensure job1 is sorted before job2 - // simulate child that's triggered by the same reactive change that // triggers its toggle - const job2 = effect(() => spy()) - expect(spy).toHaveBeenCalledTimes(1) + const job2 = () => spy() + expect(spy).toHaveBeenCalledTimes(0) queueJob(job1) queueJob(job2) await nextTick() - // should not be called again - expect(spy).toHaveBeenCalledTimes(1) + // should not be called + expect(spy).toHaveBeenCalledTimes(0) }) }) diff --git a/packages/runtime-core/src/apiWatch.ts b/packages/runtime-core/src/apiWatch.ts index 34c8deb1eaa..f14719b4ce2 100644 --- a/packages/runtime-core/src/apiWatch.ts +++ b/packages/runtime-core/src/apiWatch.ts @@ -1,12 +1,12 @@ import { - effect, - stop, isRef, Ref, ComputedRef, + ReactiveEffect, ReactiveEffectOptions, isReactive, - ReactiveFlags + ReactiveFlags, + EffectScheduler } from '@vue/reactivity' import { SchedulerJob, queuePreFlushCb } from './scheduler' import { @@ -244,7 +244,7 @@ function doWatch( let cleanup: () => void let onInvalidate: InvalidateCbRegistrator = (fn: () => void) => { - cleanup = runner.options.onStop = () => { + cleanup = effect.onStop = () => { callWithErrorHandling(fn, instance, ErrorCodes.WATCH_CLEANUP) } } @@ -268,12 +268,12 @@ function doWatch( let oldValue = isMultiSource ? [] : INITIAL_WATCHER_VALUE const job: SchedulerJob = () => { - if (!runner.active) { + if (!effect.active) { return } if (cb) { // watch(source, cb) - const newValue = runner() + const newValue = effect.run() if ( deep || forceTrigger || @@ -300,7 +300,7 @@ function doWatch( } } else { // watchEffect - runner() + effect.run() } } @@ -308,7 +308,7 @@ function doWatch( // it is allowed to self-trigger (#1727) job.allowRecurse = !!cb - let scheduler: ReactiveEffectOptions['scheduler'] + let scheduler: EffectScheduler if (flush === 'sync') { scheduler = job as any // the scheduler function gets called directly } else if (flush === 'post') { @@ -326,32 +326,35 @@ function doWatch( } } - const runner = effect(getter, { - lazy: true, - onTrack, - onTrigger, - scheduler - }) + const effect = new ReactiveEffect(getter, scheduler) - recordInstanceBoundEffect(runner, instance) + if (__DEV__) { + effect.onTrack = onTrack + effect.onTrigger = onTrigger + } + + recordInstanceBoundEffect(effect, instance) // initial run if (cb) { if (immediate) { job() } else { - oldValue = runner() + oldValue = effect.run() } } else if (flush === 'post') { - queuePostRenderEffect(runner, instance && instance.suspense) + queuePostRenderEffect( + effect.run.bind(effect), + instance && instance.suspense + ) } else { - runner() + effect.run() } return () => { - stop(runner) + effect.stop() if (instance) { - remove(instance.effects!, runner) + remove(instance.effects!, effect) } } } diff --git a/packages/runtime-core/src/compat/global.ts b/packages/runtime-core/src/compat/global.ts index 7823cc6a7e1..f843b5ead14 100644 --- a/packages/runtime-core/src/compat/global.ts +++ b/packages/runtime-core/src/compat/global.ts @@ -1,7 +1,6 @@ import { isReactive, reactive, - stop, track, TrackOpTypes, trigger, @@ -575,7 +574,7 @@ function installCompatMount( // stop effects if (effects) { for (let i = 0; i < effects.length; i++) { - stop(effects[i]) + effects[i].stop() } } // unmounted hook diff --git a/packages/runtime-core/src/component.ts b/packages/runtime-core/src/component.ts index 172e24332ea..4da57d44368 100644 --- a/packages/runtime-core/src/component.ts +++ b/packages/runtime-core/src/component.ts @@ -59,6 +59,7 @@ import { currentRenderingInstance } from './componentRenderContext' import { startMeasure, endMeasure } from './profiling' import { convertLegacyRenderFn } from './compat/renderFn' import { globalCompatConfig, validateCompatConfig } from './compat/compatConfig' +import { SchedulerJob } from './scheduler' export type Data = Record @@ -217,9 +218,14 @@ export interface ComponentInternalInstance { */ subTree: VNode /** - * The reactive effect for rendering and patching the component. Callable. + * Main update effect + * @internal + */ + effect: ReactiveEffect + /** + * Bound effect runner to be passed to schedulers */ - update: ReactiveEffect + update: SchedulerJob /** * The render function that returns vdom tree. * @internal @@ -445,6 +451,7 @@ export function createComponentInstance( root: null!, // to be immediately set next: null, subTree: null!, // will be set synchronously right after creation + effect: null!, // will be set synchronously right after creation update: null!, // will be set synchronously right after creation render: null, proxy: null, diff --git a/packages/runtime-core/src/renderer.ts b/packages/runtime-core/src/renderer.ts index dd2661ead71..d5794ba7018 100644 --- a/packages/runtime-core/src/renderer.ts +++ b/packages/runtime-core/src/renderer.ts @@ -48,15 +48,13 @@ import { flushPostFlushCbs, invalidateJob, flushPreFlushCbs, - SchedulerCb + SchedulerJob } from './scheduler' import { - effect, - stop, - ReactiveEffectOptions, isRef, pauseTracking, - resetTracking + resetTracking, + ReactiveEffect } from '@vue/reactivity' import { updateProps } from './componentProps' import { updateSlots } from './componentSlots' @@ -287,23 +285,6 @@ export const enum MoveType { REORDER } -const prodEffectOptions = { - scheduler: queueJob, - // #1801, #2043 component render effects should allow recursive updates - allowRecurse: true -} - -function createDevEffectOptions( - instance: ComponentInternalInstance -): ReactiveEffectOptions { - return { - scheduler: queueJob, - allowRecurse: true, - onTrack: instance.rtc ? e => invokeArrayFns(instance.rtc!, e) : void 0, - onTrigger: instance.rtg ? e => invokeArrayFns(instance.rtg!, e) : void 0 - } -} - export const queuePostRenderEffect = __FEATURE_SUSPENSE__ ? queueEffectWithSuspense : queuePostFlushCb @@ -379,7 +360,7 @@ export const setRef = ( // null values means this is unmount and it should not overwrite another // ref with the same key if (value) { - ;(doSet as SchedulerCb).id = -1 + ;(doSet as SchedulerJob).id = -1 queuePostRenderEffect(doSet, parentSuspense) } else { doSet() @@ -389,7 +370,7 @@ export const setRef = ( ref.value = value } if (value) { - ;(doSet as SchedulerCb).id = -1 + ;(doSet as SchedulerJob).id = -1 queuePostRenderEffect(doSet, parentSuspense) } else { doSet() @@ -1405,7 +1386,7 @@ function baseCreateRenderer( // in case the child component is also queued, remove it to avoid // double updating the same child component in the same flush. invalidateJob(instance.update) - // instance.update is the reactive effect runner. + // instance.update is the reactive effect. instance.update() } } else { @@ -1425,8 +1406,7 @@ function baseCreateRenderer( isSVG, optimized ) => { - // create reactive effect for rendering - instance.update = effect(function componentEffect() { + const componentUpdateFn = () => { if (!instance.isMounted) { let vnodeHook: VNodeHook | null | undefined const { el, props } = initialVNode @@ -1650,12 +1630,33 @@ function baseCreateRenderer( popWarningContext() } } - }, __DEV__ ? createDevEffectOptions(instance) : prodEffectOptions) + } + + // create reactive effect for rendering + const effect = (instance.effect = new ReactiveEffect( + componentUpdateFn, + () => queueJob(instance.update), + true /* allowRecurse */ + )) + + const update = (instance.update = effect.run.bind(effect) as SchedulerJob) + update.id = instance.uid + // allowRecurse + // #1801, #2043 component render effects should allow recursive updates + update.allowRecurse = true if (__DEV__) { - // @ts-ignore - instance.update.ownerInstance = instance + effect.onTrack = instance.rtc + ? e => invokeArrayFns(instance.rtc!, e) + : void 0 + effect.onTrigger = instance.rtg + ? e => invokeArrayFns(instance.rtg!, e) + : void 0 + // @ts-ignore (for scheduler) + update.ownerInstance = instance } + + update() } const updateComponentPreRender = ( @@ -2295,7 +2296,7 @@ function baseCreateRenderer( unregisterHMR(instance) } - const { bum, effects, update, subTree, um } = instance + const { bum, effect, effects, update, subTree, um } = instance // beforeUnmount hook if (bum) { @@ -2310,13 +2311,15 @@ function baseCreateRenderer( if (effects) { for (let i = 0; i < effects.length; i++) { - stop(effects[i]) + effects[i].stop() } } // update may be null if a component is unmounted before its async // setup has resolved. - if (update) { - stop(update) + if (effect) { + effect.stop() + // so that scheduler will no longer invoke it + update.active = false unmount(subTree, instance, parentSuspense, doRemove) } // unmounted hook diff --git a/packages/runtime-core/src/scheduler.ts b/packages/runtime-core/src/scheduler.ts index adaf3c26e84..9c50040e549 100644 --- a/packages/runtime-core/src/scheduler.ts +++ b/packages/runtime-core/src/scheduler.ts @@ -3,9 +3,26 @@ import { isArray } from '@vue/shared' import { ComponentPublicInstance } from './componentPublicInstance' import { ComponentInternalInstance, getComponentName } from './component' import { warn } from './warning' -import { ReactiveEffect } from '@vue/reactivity' -export interface SchedulerJob extends Function, Partial { +export interface SchedulerJob extends Function { + id?: number + active?: boolean + /** + * Indicates whether the effect is allowed to recursively trigger itself + * when managed by the scheduler. + * + * By default, a job cannot trigger itself because some built-in method calls, + * e.g. Array.prototype.push actually performs reads as well (#1740) which + * can lead to confusing infinite loops. + * The allowed cases are component update functions and watch callbacks. + * Component update functions may update child component props, which in turn + * trigger flush: "pre" watch callbacks that mutates state that the parent + * relies on (#1801). Watch callbacks doesn't track its dependencies so if it + * triggers itself again, it's likely intentional and it is the user's + * responsibility to perform recursive state mutation that eventually + * stabilizes (#1727). + */ + allowRecurse?: boolean /** * Attached by renderer.ts when setting up a component's render effect * Used to obtain component information when reporting max recursive updates. @@ -14,8 +31,7 @@ export interface SchedulerJob extends Function, Partial { ownerInstance?: ComponentInternalInstance } -export type SchedulerCb = Function & { id?: number } -export type SchedulerCbs = SchedulerCb | SchedulerCb[] +export type SchedulerJobs = SchedulerJob | SchedulerJob[] let isFlushing = false let isFlushPending = false @@ -23,12 +39,12 @@ let isFlushPending = false const queue: SchedulerJob[] = [] let flushIndex = 0 -const pendingPreFlushCbs: SchedulerCb[] = [] -let activePreFlushCbs: SchedulerCb[] | null = null +const pendingPreFlushCbs: SchedulerJob[] = [] +let activePreFlushCbs: SchedulerJob[] | null = null let preFlushIndex = 0 -const pendingPostFlushCbs: SchedulerCb[] = [] -let activePostFlushCbs: SchedulerCb[] | null = null +const pendingPostFlushCbs: SchedulerJob[] = [] +let activePostFlushCbs: SchedulerJob[] | null = null let postFlushIndex = 0 const resolvedPromise: Promise = Promise.resolve() @@ -37,7 +53,7 @@ let currentFlushPromise: Promise | null = null let currentPreFlushParentJob: SchedulerJob | null = null const RECURSION_LIMIT = 100 -type CountMap = Map +type CountMap = Map export function nextTick( this: ComponentPublicInstance | void, @@ -106,9 +122,9 @@ export function invalidateJob(job: SchedulerJob) { } function queueCb( - cb: SchedulerCbs, - activeQueue: SchedulerCb[] | null, - pendingQueue: SchedulerCb[], + cb: SchedulerJobs, + activeQueue: SchedulerJob[] | null, + pendingQueue: SchedulerJob[], index: number ) { if (!isArray(cb)) { @@ -130,11 +146,11 @@ function queueCb( queueFlush() } -export function queuePreFlushCb(cb: SchedulerCb) { +export function queuePreFlushCb(cb: SchedulerJob) { queueCb(cb, activePreFlushCbs, pendingPreFlushCbs, preFlushIndex) } -export function queuePostFlushCb(cb: SchedulerCbs) { +export function queuePostFlushCb(cb: SchedulerJobs) { queueCb(cb, activePostFlushCbs, pendingPostFlushCbs, postFlushIndex) } @@ -206,8 +222,8 @@ export function flushPostFlushCbs(seen?: CountMap) { } } -const getId = (job: SchedulerJob | SchedulerCb) => - job.id == null ? Infinity : job.id +const getId = (job: SchedulerJob): number => + job.id == null ? Infinity : job.id! function flushJobs(seen?: CountMap) { isFlushPending = false @@ -257,13 +273,13 @@ function flushJobs(seen?: CountMap) { } } -function checkRecursiveUpdates(seen: CountMap, fn: SchedulerJob | SchedulerCb) { +function checkRecursiveUpdates(seen: CountMap, fn: SchedulerJob) { if (!seen.has(fn)) { seen.set(fn, 1) } else { const count = seen.get(fn)! if (count > RECURSION_LIMIT) { - const instance = (fn as SchedulerJob).ownerInstance + const instance = fn.ownerInstance const componentName = instance && getComponentName(instance.type) warn( `Maximum recursive updates exceeded${