fix(types): ensure correct oldValue typing based on lazy option
close #719
This commit is contained in:
parent
8e19424c04
commit
c6a9787941
@ -63,8 +63,10 @@ describe('api: watch', () => {
|
|||||||
dummy = [count, prevCount]
|
dummy = [count, prevCount]
|
||||||
// assert types
|
// assert types
|
||||||
count + 1
|
count + 1
|
||||||
|
if (prevCount) {
|
||||||
prevCount + 1
|
prevCount + 1
|
||||||
}
|
}
|
||||||
|
}
|
||||||
)
|
)
|
||||||
await nextTick()
|
await nextTick()
|
||||||
expect(dummy).toMatchObject([0, undefined])
|
expect(dummy).toMatchObject([0, undefined])
|
||||||
@ -81,7 +83,9 @@ describe('api: watch', () => {
|
|||||||
dummy = [count, prevCount]
|
dummy = [count, prevCount]
|
||||||
// assert types
|
// assert types
|
||||||
count + 1
|
count + 1
|
||||||
|
if (prevCount) {
|
||||||
prevCount + 1
|
prevCount + 1
|
||||||
|
}
|
||||||
})
|
})
|
||||||
await nextTick()
|
await nextTick()
|
||||||
expect(dummy).toMatchObject([0, undefined])
|
expect(dummy).toMatchObject([0, undefined])
|
||||||
@ -99,7 +103,9 @@ describe('api: watch', () => {
|
|||||||
dummy = [count, prevCount]
|
dummy = [count, prevCount]
|
||||||
// assert types
|
// assert types
|
||||||
count + 1
|
count + 1
|
||||||
|
if (prevCount) {
|
||||||
prevCount + 1
|
prevCount + 1
|
||||||
|
}
|
||||||
})
|
})
|
||||||
await nextTick()
|
await nextTick()
|
||||||
expect(dummy).toMatchObject([1, undefined])
|
expect(dummy).toMatchObject([1, undefined])
|
||||||
@ -377,6 +383,7 @@ describe('api: watch', () => {
|
|||||||
it('ignore lazy option when using simple callback', async () => {
|
it('ignore lazy option when using simple callback', async () => {
|
||||||
const count = ref(0)
|
const count = ref(0)
|
||||||
let dummy
|
let dummy
|
||||||
|
// @ts-ignore
|
||||||
watch(
|
watch(
|
||||||
() => {
|
() => {
|
||||||
dummy = count.value
|
dummy = count.value
|
||||||
|
@ -37,9 +37,9 @@ export type WatchEffect = (onCleanup: CleanupRegistrator) => void
|
|||||||
|
|
||||||
export type WatchSource<T = any> = Ref<T> | ComputedRef<T> | (() => T)
|
export type WatchSource<T = any> = Ref<T> | ComputedRef<T> | (() => T)
|
||||||
|
|
||||||
export type WatchCallback<T = any> = (
|
export type WatchCallback<V = any, OV = any> = (
|
||||||
value: T,
|
value: V,
|
||||||
oldValue: T,
|
oldValue: OV,
|
||||||
onCleanup: CleanupRegistrator
|
onCleanup: CleanupRegistrator
|
||||||
) => any
|
) => any
|
||||||
|
|
||||||
@ -47,10 +47,16 @@ type MapSources<T> = {
|
|||||||
[K in keyof T]: T[K] extends WatchSource<infer V> ? V : never
|
[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 type CleanupRegistrator = (invalidate: () => void) => void
|
||||||
|
|
||||||
export interface WatchOptions {
|
export interface WatchOptions<Lazy = boolean> {
|
||||||
lazy?: boolean
|
lazy?: Lazy
|
||||||
flush?: 'pre' | 'post' | 'sync'
|
flush?: 'pre' | 'post' | 'sync'
|
||||||
deep?: boolean
|
deep?: boolean
|
||||||
onTrack?: ReactiveEffectOptions['onTrack']
|
onTrack?: ReactiveEffectOptions['onTrack']
|
||||||
@ -65,23 +71,29 @@ const invoke = (fn: Function) => fn()
|
|||||||
const INITIAL_WATCHER_VALUE = {}
|
const INITIAL_WATCHER_VALUE = {}
|
||||||
|
|
||||||
// overload #1: simple effect
|
// 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
|
// overload #2: single source + cb
|
||||||
export function watch<T>(
|
export function watch<T, Lazy extends Readonly<boolean> = false>(
|
||||||
source: WatchSource<T>,
|
source: WatchSource<T>,
|
||||||
cb: WatchCallback<T>,
|
cb: WatchCallback<T, Lazy extends true ? T : (T | undefined)>,
|
||||||
options?: WatchOptions
|
options?: WatchOptions<Lazy>
|
||||||
): StopHandle
|
): StopHandle
|
||||||
|
|
||||||
// overload #3: array of multiple sources + cb
|
// overload #3: array of multiple sources + cb
|
||||||
// Readonly constraint helps the callback to correctly infer value types based
|
// 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
|
// on position in the source array. Otherwise the values will get a union type
|
||||||
// of all possible value types.
|
// 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,
|
sources: T,
|
||||||
cb: WatchCallback<MapSources<T>>,
|
cb: WatchCallback<MapSources<T>, MapOldSources<T, Lazy>>,
|
||||||
options?: WatchOptions
|
options?: WatchOptions<Lazy>
|
||||||
): StopHandle
|
): StopHandle
|
||||||
|
|
||||||
// implementation
|
// implementation
|
||||||
|
54
test-dts/watch.test-d.ts
Normal file
54
test-dts/watch.test-d.ts
Normal file
@ -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 }
|
||||||
|
)
|
Loading…
Reference in New Issue
Block a user