Browse Source

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

丶远方 2 years ago
parent
commit
a645e7aa51

+ 38 - 0
packages/runtime-core/__tests__/apiWatch.spec.ts

@@ -1205,4 +1205,42 @@ 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)
+  })
 })

+ 23 - 8
packages/runtime-core/src/apiWatch.ts

@@ -75,6 +75,7 @@ export interface WatchOptionsBase extends DebuggerOptions {
 export interface WatchOptions<Immediate = boolean> extends WatchOptionsBase {
   immediate?: Immediate
   deep?: boolean
+  once?: boolean
 }
 
 export type WatchStopHandle = () => void
@@ -172,8 +173,16 @@ export function watch<T = any, Immediate extends Readonly<boolean> = 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() "once" 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
 }