Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(type): align watch types with vue3 #927

Merged
merged 2 commits into from Apr 27, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
48 changes: 33 additions & 15 deletions src/apis/watch.ts
Expand Up @@ -32,18 +32,16 @@ export type WatchCallback<V = any, OV = any> = (
onInvalidate: InvalidateCbRegistrator
) => any

type MapSources<T> = {
[K in keyof T]: T[K] extends WatchSource<infer V> ? V : never
}

type MapOldSources<T, Immediate> = {
declare type MapSources<T, Immediate> = {
[K in keyof T]: T[K] extends WatchSource<infer V>
? Immediate extends true
? V | undefined
: V
: never
}

type MultiWatchSources = (WatchSource<unknown> | object)[]

export interface WatchOptionsBase {
flush?: FlushMode
// onTrack?: ReactiveEffectOptions['onTrack'];
Expand Down Expand Up @@ -204,8 +202,8 @@ function patchWatcherTeardown(watcher: VueWatcher, runCleanup: () => void) {

function createWatcher(
vm: ComponentInstance,
source: WatchSource<unknown> | WatchSource<unknown>[] | WatchEffect,
cb: WatchCallback<any> | null,
source: WatchSource | WatchSource[] | WatchEffect,
cb: WatchCallback | null,
options: WatchOptions
): () => void {
if (__DEV__ && !cb) {
Expand Down Expand Up @@ -416,27 +414,47 @@ export function watchSyncEffect(effect: WatchEffect) {
return watchEffect(effect, { flush: 'sync' })
}

// overload #1: 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.
// overload #1 + #2 + #3: array of multiple sources + cb

// overload #1: In readonly case the first overload must be spread tuple type.
// In otherwise members in tuple can not get the correct types.
export function watch<
T extends Readonly<MultiWatchSources>,
Immediate extends Readonly<boolean> = false
>(
sources: [...T],
cb: WatchCallback<MapSources<T, false>, MapSources<T, Immediate>>,
options?: WatchOptions<Immediate>
): WatchStopHandle

// overload #2: for not spread readonly tuple
export function watch<
T extends Readonly<MultiWatchSources>,
Immediate extends Readonly<boolean> = false
>(
sources: T,
cb: WatchCallback<MapSources<T, false>, MapSources<T, Immediate>>,
options?: WatchOptions<Immediate>
): WatchStopHandle

// overload #3: for not readonly multiSources
export function watch<
T extends Readonly<WatchSource<unknown>[]>,
T extends MultiWatchSources,
Immediate extends Readonly<boolean> = false
>(
sources: [...T],
cb: WatchCallback<MapSources<T>, MapOldSources<T, Immediate>>,
cb: WatchCallback<MapSources<T, false>, MapSources<T, Immediate>>,
options?: WatchOptions<Immediate>
): WatchStopHandle

// overload #2: single source + cb
// overload #4: single source + cb
export function watch<T, Immediate extends Readonly<boolean> = false>(
source: WatchSource<T>,
cb: WatchCallback<T, Immediate extends true ? T | undefined : T>,
options?: WatchOptions<Immediate>
): WatchStopHandle

// overload #3: watching reactive object w/ cb
// overload #5: watching reactive object w/ cb
export function watch<
T extends object,
Immediate extends Readonly<boolean> = false
Expand Down
39 changes: 33 additions & 6 deletions test-dts/watch.test-d.tsx
Expand Up @@ -10,17 +10,44 @@ watch(source, (value, oldValue) => {
expectType<string>(oldValue)
})

watch([source, source2, source3], (values, oldValues) => {
expectType<(string | number)[]>(values)
expectType<(string | number)[]>(oldValues)
})
// spread array
watch(
[source, source2, source3],
([source1, source2, source3], [oldSource1, oldSource2, oldSource3]) => {
expectType<string>(source1)
expectType<string>(source2)
expectType<number>(source3)
expectType<string>(oldSource1)
expectType<string>(oldSource2)
expectType<number>(oldSource3)
}
)

// const array
watch([source, source2, source3], (values, oldValues) => {
watch([source, source2, source3] as const, (values, oldValues) => {
expectType<Readonly<[string, string, number]>>(values)
expectType<Readonly<[string, string, number]>>(oldValues)
expectType<string>(values[0])
expectType<string>(values[1])
expectType<number>(values[2])
expectType<string>(oldValues[0])
expectType<string>(oldValues[1])
expectType<number>(oldValues[2])
})

// const spread array
watch(
[source, source2, source3] as const,
([source1, source2, source3], [oldSource1, oldSource2, oldSource3]) => {
expectType<string>(source1)
expectType<string>(source2)
expectType<number>(source3)
expectType<string>(oldSource1)
expectType<string>(oldSource2)
expectType<number>(oldSource3)
}
)

// immediate watcher's oldValue will be undefined on first run.
watch(
source,
Expand All @@ -42,7 +69,7 @@ watch(

// const array
watch(
[source, source2, source3],
[source, source2, source3] as const,
(values, oldValues) => {
expectType<Readonly<[string, string, number]>>(values)
expectType<
Expand Down
2 changes: 1 addition & 1 deletion test/v3/runtime-core/apiWatch.spec.ts
Expand Up @@ -143,7 +143,7 @@ describe('api: watch', () => {
const status = ref(false)

let dummy
watch([() => state.count, status], (vals, oldVals) => {
watch([() => state.count, status] as const, (vals, oldVals) => {
dummy = [vals, oldVals]
const [count] = vals
const [, oldStatus] = oldVals
Expand Down