refactor(baseWatch): rename onEffectCleanup to onWatcherCleanup and getCurrentEffect to getCurrentWatcher for clarity
This commit is contained in:
parent
db4040d13a
commit
46761880e9
|
@ -5,7 +5,7 @@ import {
|
|||
type SchedulerJob,
|
||||
type WatchScheduler,
|
||||
baseWatch,
|
||||
onEffectCleanup,
|
||||
onWatcherCleanup,
|
||||
ref,
|
||||
} from '../src'
|
||||
|
||||
|
@ -72,7 +72,7 @@ describe('baseWatch', () => {
|
|||
const effect = baseWatch(
|
||||
source,
|
||||
() => {
|
||||
onEffectCleanup(() => {
|
||||
onWatcherCleanup(() => {
|
||||
throw 'oops in cleanup'
|
||||
})
|
||||
throw 'oops in watch'
|
||||
|
@ -102,7 +102,7 @@ describe('baseWatch', () => {
|
|||
])
|
||||
})
|
||||
|
||||
test('baseWatch with onEffectCleanup', async () => {
|
||||
test('baseWatch with onWatcherCleanup', async () => {
|
||||
let dummy = 0
|
||||
let source: Ref<number>
|
||||
const scope = new EffectScope()
|
||||
|
@ -113,8 +113,8 @@ describe('baseWatch', () => {
|
|||
source.value
|
||||
|
||||
onCleanup(() => (dummy += 2))
|
||||
onEffectCleanup(() => (dummy += 3))
|
||||
onEffectCleanup(() => (dummy += 5))
|
||||
onWatcherCleanup(() => (dummy += 3))
|
||||
onWatcherCleanup(() => (dummy += 5))
|
||||
})
|
||||
})
|
||||
expect(dummy).toBe(0)
|
||||
|
@ -133,7 +133,7 @@ describe('baseWatch', () => {
|
|||
expect(dummy).toBe(30)
|
||||
})
|
||||
|
||||
test('nested calls to baseWatch and onEffectCleanup', async () => {
|
||||
test('nested calls to baseWatch and onWatcherCleanup', async () => {
|
||||
let calls: string[] = []
|
||||
let source: Ref<number>
|
||||
let copyist: Ref<number>
|
||||
|
@ -146,7 +146,7 @@ describe('baseWatch', () => {
|
|||
baseWatch(
|
||||
() => {
|
||||
const current = (copyist.value = source.value)
|
||||
onEffectCleanup(() => calls.push(`sync ${current}`))
|
||||
onWatcherCleanup(() => calls.push(`sync ${current}`))
|
||||
},
|
||||
null,
|
||||
{},
|
||||
|
@ -155,7 +155,7 @@ describe('baseWatch', () => {
|
|||
baseWatch(
|
||||
() => {
|
||||
const current = copyist.value
|
||||
onEffectCleanup(() => calls.push(`post ${current}`))
|
||||
onWatcherCleanup(() => calls.push(`post ${current}`))
|
||||
},
|
||||
null,
|
||||
{ scheduler },
|
||||
|
@ -180,6 +180,7 @@ describe('baseWatch', () => {
|
|||
scope.stop()
|
||||
expect(calls).toEqual(['sync 2', 'post 2'])
|
||||
})
|
||||
|
||||
test('baseWatch with middleware', async () => {
|
||||
let effectCalls: string[] = []
|
||||
let watchCalls: string[] = []
|
||||
|
@ -190,7 +191,7 @@ describe('baseWatch', () => {
|
|||
() => {
|
||||
source.value
|
||||
effectCalls.push('effect')
|
||||
onEffectCleanup(() => effectCalls.push('effect cleanup'))
|
||||
onWatcherCleanup(() => effectCalls.push('effect cleanup'))
|
||||
},
|
||||
null,
|
||||
{
|
||||
|
@ -207,7 +208,7 @@ describe('baseWatch', () => {
|
|||
() => source.value,
|
||||
() => {
|
||||
watchCalls.push('watch')
|
||||
onEffectCleanup(() => watchCalls.push('watch cleanup'))
|
||||
onWatcherCleanup(() => watchCalls.push('watch cleanup'))
|
||||
},
|
||||
{
|
||||
scheduler,
|
||||
|
|
|
@ -15,7 +15,7 @@ import type { ComputedRef } from './computed'
|
|||
import { ReactiveFlags } from './constants'
|
||||
import {
|
||||
type DebuggerOptions,
|
||||
type EffectScheduler,
|
||||
EffectFlags,
|
||||
ReactiveEffect,
|
||||
pauseTracking,
|
||||
resetTracking,
|
||||
|
@ -46,7 +46,7 @@ export interface BaseWatchOptions<Immediate = boolean> extends DebuggerOptions {
|
|||
immediate?: Immediate
|
||||
deep?: boolean
|
||||
once?: boolean
|
||||
scheduler?: Scheduler
|
||||
scheduler?: WatchScheduler
|
||||
middleware?: BaseWatchMiddleware
|
||||
onError?: HandleError
|
||||
onWarn?: HandleWarn
|
||||
|
@ -55,22 +55,41 @@ export interface BaseWatchOptions<Immediate = boolean> extends DebuggerOptions {
|
|||
// initial value for watchers to trigger on undefined initial values
|
||||
const INITIAL_WATCHER_VALUE = {}
|
||||
|
||||
export type Scheduler = (
|
||||
export type WatchScheduler = (
|
||||
job: SchedulerJob,
|
||||
effect: ReactiveEffect,
|
||||
isInit: boolean,
|
||||
immediateFirstRun: boolean,
|
||||
hasCb: boolean,
|
||||
) => void
|
||||
export type BaseWatchMiddleware = (next: () => unknown) => any
|
||||
export type HandleError = (err: unknown, type: BaseWatchErrorCodes) => void
|
||||
export type HandleWarn = (msg: string, ...args: any[]) => void
|
||||
|
||||
const DEFAULT_SCHEDULER: Scheduler = job => job()
|
||||
const DEFAULT_SCHEDULER: WatchScheduler = (
|
||||
job,
|
||||
effect,
|
||||
immediateFirstRun,
|
||||
hasCb,
|
||||
) => {
|
||||
if (immediateFirstRun) {
|
||||
!hasCb && effect.run()
|
||||
} else {
|
||||
job()
|
||||
}
|
||||
}
|
||||
const DEFAULT_HANDLE_ERROR: HandleError = (err: unknown) => {
|
||||
throw err
|
||||
}
|
||||
|
||||
const cleanupMap: WeakMap<ReactiveEffect, (() => void)[]> = new WeakMap()
|
||||
let activeEffect: ReactiveEffect | undefined = undefined
|
||||
let activeWatcher: ReactiveEffect | undefined = undefined
|
||||
|
||||
/**
|
||||
* Returns the current active effect if there is one.
|
||||
*/
|
||||
export function getCurrentWatcher() {
|
||||
return activeWatcher
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a cleanup callback on the current active effect. This
|
||||
|
@ -79,15 +98,15 @@ let activeEffect: ReactiveEffect | undefined = undefined
|
|||
*
|
||||
* @param cleanupFn - The callback function to attach to the effect's cleanup.
|
||||
*/
|
||||
export function onEffectCleanup(cleanupFn: () => void) {
|
||||
if (activeEffect) {
|
||||
export function onWatcherCleanup(cleanupFn: () => void, failSilently = false) {
|
||||
if (activeWatcher) {
|
||||
const cleanups =
|
||||
cleanupMap.get(activeEffect) ||
|
||||
cleanupMap.set(activeEffect, []).get(activeEffect)!
|
||||
cleanupMap.get(activeWatcher) ||
|
||||
cleanupMap.set(activeWatcher, []).get(activeWatcher)!
|
||||
cleanups.push(cleanupFn)
|
||||
} else if (__DEV__) {
|
||||
} else if (__DEV__ && !failSilently) {
|
||||
warn(
|
||||
`onEffectCleanup() was called when there was no active effect` +
|
||||
`onWatcherCleanup() was called when there was no active watcher` +
|
||||
` to associate with.`,
|
||||
)
|
||||
}
|
||||
|
@ -170,17 +189,17 @@ export function baseWatch(
|
|||
resetTracking()
|
||||
}
|
||||
}
|
||||
const currentEffect = activeEffect
|
||||
activeEffect = effect
|
||||
const currentEffect = activeWatcher
|
||||
activeWatcher = effect
|
||||
try {
|
||||
return callWithAsyncErrorHandling(
|
||||
source,
|
||||
onError,
|
||||
BaseWatchErrorCodes.WATCH_CALLBACK,
|
||||
[onEffectCleanup],
|
||||
[onWatcherCleanup],
|
||||
)
|
||||
} finally {
|
||||
activeEffect = currentEffect
|
||||
activeWatcher = currentEffect
|
||||
}
|
||||
}
|
||||
if (middleware) {
|
||||
|
@ -198,31 +217,20 @@ export function baseWatch(
|
|||
getter = () => traverse(baseGetter())
|
||||
}
|
||||
|
||||
const scope = getCurrentScope()
|
||||
|
||||
if (once) {
|
||||
if (!cb) {
|
||||
// onEffectCleanup need use effect as a key
|
||||
scope?.effects.push((effect = {} as any))
|
||||
getter()
|
||||
return
|
||||
}
|
||||
if (immediate) {
|
||||
// onEffectCleanup need use effect as a key
|
||||
scope?.effects.push((effect = {} as any))
|
||||
callWithAsyncErrorHandling(
|
||||
cb,
|
||||
onError,
|
||||
BaseWatchErrorCodes.WATCH_CALLBACK,
|
||||
[getter(), isMultiSource ? [] : undefined, onEffectCleanup],
|
||||
)
|
||||
return
|
||||
}
|
||||
if (cb) {
|
||||
const _cb = cb
|
||||
cb = (...args) => {
|
||||
_cb(...args)
|
||||
effect?.stop()
|
||||
}
|
||||
} else {
|
||||
const _getter = getter
|
||||
getter = () => {
|
||||
_getter()
|
||||
effect?.stop()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let oldValue: any = isMultiSource
|
||||
|
@ -250,8 +258,8 @@ export function baseWatch(
|
|||
if (cleanup) {
|
||||
cleanup()
|
||||
}
|
||||
const currentEffect = activeEffect
|
||||
activeEffect = effect
|
||||
const currentWatcher = activeWatcher
|
||||
activeWatcher = effect
|
||||
try {
|
||||
callWithAsyncErrorHandling(
|
||||
cb!,
|
||||
|
@ -265,12 +273,12 @@ export function baseWatch(
|
|||
: isMultiSource && oldValue[0] === INITIAL_WATCHER_VALUE
|
||||
? []
|
||||
: oldValue,
|
||||
onEffectCleanup,
|
||||
onWatcherCleanup,
|
||||
],
|
||||
)
|
||||
oldValue = newValue
|
||||
} finally {
|
||||
activeEffect = currentEffect
|
||||
activeWatcher = currentWatcher
|
||||
}
|
||||
}
|
||||
if (middleware) {
|
||||
|
|
|
@ -80,11 +80,12 @@ export { reactiveReadArray, shallowReadArray } from './arrayInstrumentations'
|
|||
export { TrackOpTypes, TriggerOpTypes, ReactiveFlags } from './constants'
|
||||
export {
|
||||
baseWatch,
|
||||
onEffectCleanup,
|
||||
getCurrentWatcher,
|
||||
traverse,
|
||||
onWatcherCleanup,
|
||||
BaseWatchErrorCodes,
|
||||
type BaseWatchOptions,
|
||||
type BaseWatchMiddleware,
|
||||
type Scheduler,
|
||||
type SchedulerJob,
|
||||
type WatchScheduler,
|
||||
} from './baseWatch'
|
||||
export { type SchedulerJob, SchedulerJobFlags } from './scheduler'
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
export enum SchedulerJobFlags {
|
||||
QUEUED = 1 << 0,
|
||||
PRE = 1 << 1,
|
||||
/**
|
||||
* 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).
|
||||
*/
|
||||
ALLOW_RECURSE = 1 << 2,
|
||||
DISPOSED = 1 << 3,
|
||||
}
|
||||
|
||||
export interface SchedulerJob extends Function {
|
||||
id?: number
|
||||
/**
|
||||
* flags can technically be undefined, but it can still be used in bitwise
|
||||
* operations just like 0.
|
||||
*/
|
||||
flags?: SchedulerJobFlags
|
||||
}
|
|
@ -5,7 +5,7 @@ import {
|
|||
defineComponent,
|
||||
getCurrentInstance,
|
||||
nextTick,
|
||||
onEffectCleanup,
|
||||
onWatcherCleanup,
|
||||
reactive,
|
||||
ref,
|
||||
watch,
|
||||
|
@ -395,17 +395,17 @@ describe('api: watch', () => {
|
|||
expect(cleanup).toHaveBeenCalledTimes(2)
|
||||
})
|
||||
|
||||
it('onEffectCleanup', async () => {
|
||||
it('onWatcherCleanup', async () => {
|
||||
const count = ref(0)
|
||||
const cleanupEffect = vi.fn()
|
||||
const cleanupWatch = vi.fn()
|
||||
|
||||
const stopEffect = watchEffect(() => {
|
||||
onEffectCleanup(cleanupEffect)
|
||||
onWatcherCleanup(cleanupEffect)
|
||||
count.value
|
||||
})
|
||||
const stopWatch = watch(count, () => {
|
||||
onEffectCleanup(cleanupWatch)
|
||||
onWatcherCleanup(cleanupWatch)
|
||||
})
|
||||
|
||||
count.value++
|
||||
|
|
|
@ -28,7 +28,7 @@ export {
|
|||
// effect
|
||||
effect,
|
||||
stop,
|
||||
onEffectCleanup,
|
||||
onWatcherCleanup,
|
||||
ReactiveEffect,
|
||||
// effect scope
|
||||
effectScope,
|
||||
|
|
|
@ -2,13 +2,13 @@ import type { Ref } from '@vue/reactivity'
|
|||
import {
|
||||
EffectScope,
|
||||
nextTick,
|
||||
onEffectCleanup,
|
||||
onWatcherCleanup,
|
||||
ref,
|
||||
watchEffect,
|
||||
watchSyncEffect,
|
||||
} from '../src'
|
||||
|
||||
describe('watchEffect and onEffectCleanup', () => {
|
||||
describe('watchEffect and onWatcherCleanup', () => {
|
||||
test('basic', async () => {
|
||||
let dummy = 0
|
||||
let source: Ref<number>
|
||||
|
@ -20,8 +20,8 @@ describe('watchEffect and onEffectCleanup', () => {
|
|||
source.value
|
||||
|
||||
onCleanup(() => (dummy += 2))
|
||||
onEffectCleanup(() => (dummy += 3))
|
||||
onEffectCleanup(() => (dummy += 5))
|
||||
onWatcherCleanup(() => (dummy += 3))
|
||||
onWatcherCleanup(() => (dummy += 5))
|
||||
})
|
||||
})
|
||||
await nextTick()
|
||||
|
@ -55,11 +55,11 @@ describe('watchEffect and onEffectCleanup', () => {
|
|||
double = ref(0)
|
||||
watchEffect(() => {
|
||||
double.value = source.value * 2
|
||||
onEffectCleanup(() => (dummy += 2))
|
||||
onWatcherCleanup(() => (dummy += 2))
|
||||
})
|
||||
watchSyncEffect(() => {
|
||||
double.value
|
||||
onEffectCleanup(() => (dummy += 3))
|
||||
onWatcherCleanup(() => (dummy += 3))
|
||||
})
|
||||
})
|
||||
await nextTick()
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import {
|
||||
nextTick,
|
||||
onBeforeUpdate,
|
||||
onEffectCleanup,
|
||||
onUpdated,
|
||||
onWatcherCleanup,
|
||||
ref,
|
||||
renderEffect,
|
||||
renderWatch,
|
||||
|
@ -101,17 +101,17 @@ describe('renderWatch', () => {
|
|||
watchPostEffect(() => {
|
||||
const current = source.value
|
||||
calls.push(`post ${current}`)
|
||||
onEffectCleanup(() => calls.push(`post cleanup ${current}`))
|
||||
onWatcherCleanup(() => calls.push(`post cleanup ${current}`))
|
||||
})
|
||||
watchEffect(() => {
|
||||
const current = source.value
|
||||
calls.push(`pre ${current}`)
|
||||
onEffectCleanup(() => calls.push(`pre cleanup ${current}`))
|
||||
onWatcherCleanup(() => calls.push(`pre cleanup ${current}`))
|
||||
})
|
||||
watchSyncEffect(() => {
|
||||
const current = source.value
|
||||
calls.push(`sync ${current}`)
|
||||
onEffectCleanup(() => calls.push(`sync cleanup ${current}`))
|
||||
onWatcherCleanup(() => calls.push(`sync cleanup ${current}`))
|
||||
})
|
||||
return { source, change, renderSource, changeRender }
|
||||
},
|
||||
|
@ -121,13 +121,13 @@ describe('renderWatch', () => {
|
|||
renderEffect(() => {
|
||||
const current = _ctx.renderSource
|
||||
calls.push(`renderEffect ${current}`)
|
||||
onEffectCleanup(() => calls.push(`renderEffect cleanup ${current}`))
|
||||
onWatcherCleanup(() => calls.push(`renderEffect cleanup ${current}`))
|
||||
})
|
||||
renderWatch(
|
||||
() => _ctx.renderSource,
|
||||
value => {
|
||||
calls.push(`renderWatch ${value}`)
|
||||
onEffectCleanup(() => calls.push(`renderWatch cleanup ${value}`))
|
||||
onWatcherCleanup(() => calls.push(`renderWatch cleanup ${value}`))
|
||||
},
|
||||
)
|
||||
},
|
||||
|
|
|
@ -29,7 +29,7 @@ export {
|
|||
// effect
|
||||
stop,
|
||||
ReactiveEffect,
|
||||
onEffectCleanup,
|
||||
onWatcherCleanup,
|
||||
// effect scope
|
||||
effectScope,
|
||||
EffectScope,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<script setup lang="ts">
|
||||
import {
|
||||
onEffectCleanup,
|
||||
onWatcherCleanup,
|
||||
ref,
|
||||
watch,
|
||||
watchEffect,
|
||||
|
@ -14,30 +14,30 @@ const add = () => source.value++
|
|||
watchPostEffect(() => {
|
||||
const current = source.value
|
||||
console.log('post', current)
|
||||
onEffectCleanup(() => console.log('cleanup post', current))
|
||||
onWatcherCleanup(() => console.log('cleanup post', current))
|
||||
})
|
||||
|
||||
watchEffect(() => {
|
||||
const current = source.value
|
||||
console.log('pre', current)
|
||||
onEffectCleanup(() => console.log('cleanup pre', current))
|
||||
onWatcherCleanup(() => console.log('cleanup pre', current))
|
||||
})
|
||||
|
||||
watchSyncEffect(() => {
|
||||
const current = source.value
|
||||
console.log('sync', current)
|
||||
onEffectCleanup(() => console.log('cleanup sync', current))
|
||||
onWatcherCleanup(() => console.log('cleanup sync', current))
|
||||
})
|
||||
|
||||
watch(source, (value, oldValue) => {
|
||||
console.log('sync watch', value, 'oldValue:', oldValue)
|
||||
onEffectCleanup(() => console.log('cleanup sync watch', value))
|
||||
console.log('watch', value, 'oldValue:', oldValue)
|
||||
onWatcherCleanup(() => console.log('cleanup watch', value))
|
||||
})
|
||||
|
||||
const onUpdate = (arg: any) => {
|
||||
const current = source.value
|
||||
console.log('render', current)
|
||||
onEffectCleanup(() => console.log('cleanup render', current))
|
||||
onWatcherCleanup(() => console.log('cleanup render', current))
|
||||
return arg
|
||||
}
|
||||
</script>
|
||||
|
|
Loading…
Reference in New Issue