feat(runtime-core): add `once` option to watch (#9034)

This commit is contained in:
丶远方 2023-08-30 15:25:51 +08:00 committed by 三咲智子 Kevin Deng
parent edf2572615
commit a645e7aa51
No known key found for this signature in database
GPG Key ID: 69992F2250DFD93E
2 changed files with 61 additions and 8 deletions

View File

@ -1205,4 +1205,42 @@ describe('api: watch', () => {
expect(countWE).toBe(3) expect(countWE).toBe(3)
expect(countW).toBe(2) expect(countW).toBe(2)
}) })
const options = [
{ name: 'only trigger once watch' },
{
deep: true,
name: 'only trigger once watch with deep'
},
{
flush: 'sync',
name: 'only trigger once watch with flush: sync'
},
{
flush: 'pre',
name: 'only trigger once watch with flush: pre'
},
{
immediate: true,
name: 'only trigger once watch with immediate'
}
] as const
test.each(options)('$name', async option => {
const count = ref(0)
const cb = vi.fn()
watch(count, cb, { once: true, ...option })
count.value++
await nextTick()
expect(count.value).toBe(1)
expect(cb).toHaveBeenCalledTimes(1)
count.value++
await nextTick()
expect(count.value).toBe(2)
expect(cb).toHaveBeenCalledTimes(1)
})
}) })

View File

@ -75,6 +75,7 @@ export interface WatchOptionsBase extends DebuggerOptions {
export interface WatchOptions<Immediate = boolean> extends WatchOptionsBase { export interface WatchOptions<Immediate = boolean> extends WatchOptionsBase {
immediate?: Immediate immediate?: Immediate
deep?: boolean deep?: boolean
once?: boolean
} }
export type WatchStopHandle = () => void export type WatchStopHandle = () => void
@ -172,8 +173,16 @@ export function watch<T = any, Immediate extends Readonly<boolean> = false>(
function doWatch( function doWatch(
source: WatchSource | WatchSource[] | WatchEffect | object, source: WatchSource | WatchSource[] | WatchEffect | object,
cb: WatchCallback | null, cb: WatchCallback | null,
{ immediate, deep, flush, onTrack, onTrigger }: WatchOptions = EMPTY_OBJ { immediate, deep, flush, once, onTrack, onTrigger }: WatchOptions = EMPTY_OBJ
): WatchStopHandle { ): WatchStopHandle {
if (cb && once) {
const _cb = cb
cb = (...args) => {
_cb(...args)
unwatch()
}
}
if (__DEV__ && !cb) { if (__DEV__ && !cb) {
if (immediate !== undefined) { if (immediate !== undefined) {
warn( warn(
@ -187,6 +196,12 @@ function doWatch(
`watch(source, callback, options?) signature.` `watch(source, callback, options?) signature.`
) )
} }
if (once !== undefined) {
warn(
`watch() "once" option is only respected when using the ` +
`watch(source, callback, options?) signature.`
)
}
} }
const warnInvalidSource = (s: unknown) => { const warnInvalidSource = (s: unknown) => {
@ -363,6 +378,13 @@ function doWatch(
const effect = new ReactiveEffect(getter, scheduler) const effect = new ReactiveEffect(getter, scheduler)
const unwatch = () => {
effect.stop()
if (instance && instance.scope) {
remove(instance.scope.effects!, effect)
}
}
if (__DEV__) { if (__DEV__) {
effect.onTrack = onTrack effect.onTrack = onTrack
effect.onTrigger = onTrigger effect.onTrigger = onTrigger
@ -384,13 +406,6 @@ function doWatch(
effect.run() effect.run()
} }
const unwatch = () => {
effect.stop()
if (instance && instance.scope) {
remove(instance.scope.effects!, effect)
}
}
if (__SSR__ && ssrCleanup) ssrCleanup.push(unwatch) if (__SSR__ && ssrCleanup) ssrCleanup.push(unwatch)
return unwatch return unwatch
} }