dummy = [count, prevCount]
// assert types
count + 1
- prevCount + 1
+ if (prevCount) {
+ prevCount + 1
+ }
}
)
await nextTick()
dummy = [count, prevCount]
// assert types
count + 1
- prevCount + 1
+ if (prevCount) {
+ prevCount + 1
+ }
})
await nextTick()
expect(dummy).toMatchObject([0, undefined])
dummy = [count, prevCount]
// assert types
count + 1
- prevCount + 1
+ if (prevCount) {
+ prevCount + 1
+ }
})
await nextTick()
expect(dummy).toMatchObject([1, undefined])
it('ignore lazy option when using simple callback', async () => {
const count = ref(0)
let dummy
+ // @ts-ignore
watch(
() => {
dummy = count.value
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
[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']
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
--- /dev/null
+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 }
+)