]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix(types): ensure correct oldValue typing based on lazy option
authorEvan You <yyx990803@gmail.com>
Thu, 13 Feb 2020 17:08:21 +0000 (12:08 -0500)
committerEvan You <yyx990803@gmail.com>
Thu, 13 Feb 2020 17:09:13 +0000 (12:09 -0500)
close #719

packages/runtime-core/__tests__/apiWatch.spec.ts
packages/runtime-core/src/apiWatch.ts
test-dts/watch.test-d.ts [new file with mode: 0644]

index 82a4ad368111ad519cab2061e49304f928e53ae9..9b1d35a21f08b1c6cfea4c608933e13f4bca6c31 100644 (file)
@@ -63,7 +63,9 @@ describe('api: watch', () => {
         dummy = [count, prevCount]
         // assert types
         count + 1
-        prevCount + 1
+        if (prevCount) {
+          prevCount + 1
+        }
       }
     )
     await nextTick()
@@ -81,7 +83,9 @@ describe('api: watch', () => {
       dummy = [count, prevCount]
       // assert types
       count + 1
-      prevCount + 1
+      if (prevCount) {
+        prevCount + 1
+      }
     })
     await nextTick()
     expect(dummy).toMatchObject([0, undefined])
@@ -99,7 +103,9 @@ describe('api: watch', () => {
       dummy = [count, prevCount]
       // assert types
       count + 1
-      prevCount + 1
+      if (prevCount) {
+        prevCount + 1
+      }
     })
     await nextTick()
     expect(dummy).toMatchObject([1, undefined])
@@ -377,6 +383,7 @@ describe('api: watch', () => {
   it('ignore lazy option when using simple callback', async () => {
     const count = ref(0)
     let dummy
+    // @ts-ignore
     watch(
       () => {
         dummy = count.value
index c4bcdd58f0dcddef667aec01cd0a6e4d4bb9978c..1c930a09da304260055a7d9cb1ad031e0f745950 100644 (file)
@@ -37,9 +37,9 @@ export type WatchEffect = (onCleanup: CleanupRegistrator) => void
 
 export type WatchSource<T = any> = Ref<T> | ComputedRef<T> | (() => T)
 
-export type WatchCallback<T = any> = (
-  value: T,
-  oldValue: T,
+export type WatchCallback<V = any, OV = any> = (
+  value: V,
+  oldValue: OV,
   onCleanup: CleanupRegistrator
 ) => any
 
@@ -47,10 +47,16 @@ type MapSources<T> = {
   [K in keyof T]: T[K] extends WatchSource<infer V> ? V : never
 }
 
+type MapOldSources<T, Lazy> = {
+  [K in keyof T]: T[K] extends WatchSource<infer V>
+    ? Lazy extends true ? V : (V | undefined)
+    : never
+}
+
 export type CleanupRegistrator = (invalidate: () => void) => void
 
-export interface WatchOptions {
-  lazy?: boolean
+export interface WatchOptions<Lazy = boolean> {
+  lazy?: Lazy
   flush?: 'pre' | 'post' | 'sync'
   deep?: boolean
   onTrack?: ReactiveEffectOptions['onTrack']
@@ -65,23 +71,29 @@ const invoke = (fn: Function) => fn()
 const INITIAL_WATCHER_VALUE = {}
 
 // overload #1: simple effect
-export function watch(effect: WatchEffect, options?: WatchOptions): StopHandle
+export function watch(
+  effect: WatchEffect,
+  options?: WatchOptions<false>
+): StopHandle
 
 // overload #2: single source + cb
-export function watch<T>(
+export function watch<T, Lazy extends Readonly<boolean> = false>(
   source: WatchSource<T>,
-  cb: WatchCallback<T>,
-  options?: WatchOptions
+  cb: WatchCallback<T, Lazy extends true ? T : (T | undefined)>,
+  options?: WatchOptions<Lazy>
 ): StopHandle
 
 // overload #3: array of multiple sources + cb
 // Readonly constraint helps the callback to correctly infer value types based
 // on position in the source array. Otherwise the values will get a union type
 // of all possible value types.
-export function watch<T extends Readonly<WatchSource<unknown>[]>>(
+export function watch<
+  T extends Readonly<WatchSource<unknown>[]>,
+  Lazy extends Readonly<boolean> = false
+>(
   sources: T,
-  cb: WatchCallback<MapSources<T>>,
-  options?: WatchOptions
+  cb: WatchCallback<MapSources<T>, MapOldSources<T, Lazy>>,
+  options?: WatchOptions<Lazy>
 ): StopHandle
 
 // implementation
diff --git a/test-dts/watch.test-d.ts b/test-dts/watch.test-d.ts
new file mode 100644 (file)
index 0000000..d32313c
--- /dev/null
@@ -0,0 +1,54 @@
+import { ref, computed, watch } from './index'
+import { expectType } from 'tsd'
+
+const source = ref('foo')
+const source2 = computed(() => source.value)
+const source3 = () => 1
+
+// eager watcher's oldValue will be undefined on first run.
+watch(source, (value, oldValue) => {
+  expectType<string>(value)
+  expectType<string | undefined>(oldValue)
+})
+
+watch([source, source2, source3], (values, oldValues) => {
+  expectType<(string | number)[]>(values)
+  expectType<(string | number | undefined)[]>(oldValues)
+})
+
+// const array
+watch([source, source2, source3] as const, (values, oldValues) => {
+  expectType<Readonly<[string, string, number]>>(values)
+  expectType<
+    Readonly<[string | undefined, string | undefined, number | undefined]>
+  >(oldValues)
+})
+
+// lazy watcher will have consistent types for oldValue.
+watch(
+  source,
+  (value, oldValue) => {
+    expectType<string>(value)
+    expectType<string>(oldValue)
+  },
+  { lazy: true }
+)
+
+watch(
+  [source, source2, source3],
+  (values, oldValues) => {
+    expectType<(string | number)[]>(values)
+    expectType<(string | number)[]>(oldValues)
+  },
+  { lazy: true }
+)
+
+// const array
+watch(
+  [source, source2, source3] as const,
+  (values, oldValues) => {
+    expectType<Readonly<[string, string, number]>>(values)
+    expectType<Readonly<[string, string, number]>>(oldValues)
+  },
+  { lazy: true }
+)