Skip to content

Commit

Permalink
fix(types): ensure correct oldValue typing based on lazy option
Browse files Browse the repository at this point in the history
close #719
  • Loading branch information
yyx990803 committed Feb 13, 2020
1 parent 8e19424 commit c6a9787
Show file tree
Hide file tree
Showing 3 changed files with 88 additions and 15 deletions.
13 changes: 10 additions & 3 deletions packages/runtime-core/__tests__/apiWatch.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,9 @@ describe('api: watch', () => {
dummy = [count, prevCount]
// assert types
count + 1
prevCount + 1
if (prevCount) {
prevCount + 1
}
}
)
await nextTick()
Expand All @@ -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])
Expand All @@ -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])
Expand Down Expand Up @@ -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
Expand Down
36 changes: 24 additions & 12 deletions packages/runtime-core/src/apiWatch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,20 +37,26 @@ 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

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']
Expand All @@ -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
Expand Down
54 changes: 54 additions & 0 deletions test-dts/watch.test-d.ts
Original file line number Diff line number Diff line change
@@ -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 }
)

0 comments on commit c6a9787

Please sign in to comment.