From a2da7a2997543d10e1a1c0317793fec626aaa8ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=B6=E8=BF=9C=E6=96=B9?= Date: Thu, 24 Aug 2023 23:13:53 +0800 Subject: [PATCH 1/3] feat(apiWatch): add `once` option to the watch function --- .../runtime-core/__tests__/apiWatch.spec.ts | 40 +++++++++++++++++++ packages/runtime-core/src/apiWatch.ts | 31 ++++++++++---- 2 files changed, 63 insertions(+), 8 deletions(-) diff --git a/packages/runtime-core/__tests__/apiWatch.spec.ts b/packages/runtime-core/__tests__/apiWatch.spec.ts index f24ce80b9df..bd5347713e9 100644 --- a/packages/runtime-core/__tests__/apiWatch.spec.ts +++ b/packages/runtime-core/__tests__/apiWatch.spec.ts @@ -32,6 +32,7 @@ import { effectScope, toRef } from '@vue/reactivity' +import { expect } from 'vitest' // reference: https://vue-composition-api-rfc.netlify.com/api.html#watch @@ -1205,4 +1206,43 @@ describe('api: watch', () => { expect(countWE).toBe(3) 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) + }) + // watch once with immediate }) diff --git a/packages/runtime-core/src/apiWatch.ts b/packages/runtime-core/src/apiWatch.ts index 1b85ba12d19..34aaff7fe9c 100644 --- a/packages/runtime-core/src/apiWatch.ts +++ b/packages/runtime-core/src/apiWatch.ts @@ -75,6 +75,7 @@ export interface WatchOptionsBase extends DebuggerOptions { export interface WatchOptions extends WatchOptionsBase { immediate?: Immediate deep?: boolean + once?: boolean } export type WatchStopHandle = () => void @@ -172,8 +173,16 @@ export function watch = false>( function doWatch( source: WatchSource | WatchSource[] | WatchEffect | object, cb: WatchCallback | null, - { immediate, deep, flush, onTrack, onTrigger }: WatchOptions = EMPTY_OBJ + { immediate, deep, flush, once, onTrack, onTrigger }: WatchOptions = EMPTY_OBJ ): WatchStopHandle { + if (cb && once) { + const _cb = cb + cb = (...args) => { + _cb(...args) + unwatch() + } + } + if (__DEV__ && !cb) { if (immediate !== undefined) { warn( @@ -187,6 +196,12 @@ function doWatch( `watch(source, callback, options?) signature.` ) } + if (once !== undefined) { + warn( + `watch() "deep" option is only respected when using the ` + + `watch(source, callback, options?) signature.` + ) + } } const warnInvalidSource = (s: unknown) => { @@ -363,6 +378,13 @@ function doWatch( const effect = new ReactiveEffect(getter, scheduler) + const unwatch = () => { + effect.stop() + if (instance && instance.scope) { + remove(instance.scope.effects!, effect) + } + } + if (__DEV__) { effect.onTrack = onTrack effect.onTrigger = onTrigger @@ -384,13 +406,6 @@ function doWatch( effect.run() } - const unwatch = () => { - effect.stop() - if (instance && instance.scope) { - remove(instance.scope.effects!, effect) - } - } - if (__SSR__ && ssrCleanup) ssrCleanup.push(unwatch) return unwatch } From 9d2c1808baaef1844fe1db337ee44700c13bbcdb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=B6=E8=BF=9C=E6=96=B9?= Date: Thu, 24 Aug 2023 23:48:58 +0800 Subject: [PATCH 2/3] chore: remove comment --- packages/runtime-core/__tests__/apiWatch.spec.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/runtime-core/__tests__/apiWatch.spec.ts b/packages/runtime-core/__tests__/apiWatch.spec.ts index bd5347713e9..bddfc5ff541 100644 --- a/packages/runtime-core/__tests__/apiWatch.spec.ts +++ b/packages/runtime-core/__tests__/apiWatch.spec.ts @@ -32,7 +32,6 @@ import { effectScope, toRef } from '@vue/reactivity' -import { expect } from 'vitest' // reference: https://vue-composition-api-rfc.netlify.com/api.html#watch @@ -1244,5 +1243,4 @@ describe('api: watch', () => { expect(count.value).toBe(2) expect(cb).toHaveBeenCalledTimes(1) }) - // watch once with immediate }) From ddba601bb9b162c7e071a6a77ab0fce7901ae7da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=B6=E8=BF=9C=E6=96=B9?= Date: Thu, 24 Aug 2023 23:53:12 +0800 Subject: [PATCH 3/3] chore: update warn msg --- packages/runtime-core/src/apiWatch.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/runtime-core/src/apiWatch.ts b/packages/runtime-core/src/apiWatch.ts index 34aaff7fe9c..c307c4198a3 100644 --- a/packages/runtime-core/src/apiWatch.ts +++ b/packages/runtime-core/src/apiWatch.ts @@ -198,7 +198,7 @@ function doWatch( } if (once !== undefined) { warn( - `watch() "deep" option is only respected when using the ` + + `watch() "once" option is only respected when using the ` + `watch(source, callback, options?) signature.` ) }