Skip to content

Commit 679f5c2

Browse files
authored
fix(type): align watch types with vue3 (#927)
1 parent 293f03b commit 679f5c2

File tree

3 files changed

+67
-22
lines changed

3 files changed

+67
-22
lines changed

src/apis/watch.ts

+33-15
Original file line numberDiff line numberDiff line change
@@ -32,18 +32,16 @@ export type WatchCallback<V = any, OV = any> = (
3232
onInvalidate: InvalidateCbRegistrator
3333
) => any
3434

35-
type MapSources<T> = {
36-
[K in keyof T]: T[K] extends WatchSource<infer V> ? V : never
37-
}
38-
39-
type MapOldSources<T, Immediate> = {
35+
declare type MapSources<T, Immediate> = {
4036
[K in keyof T]: T[K] extends WatchSource<infer V>
4137
? Immediate extends true
4238
? V | undefined
4339
: V
4440
: never
4541
}
4642

43+
type MultiWatchSources = (WatchSource<unknown> | object)[]
44+
4745
export interface WatchOptionsBase {
4846
flush?: FlushMode
4947
// onTrack?: ReactiveEffectOptions['onTrack'];
@@ -204,8 +202,8 @@ function patchWatcherTeardown(watcher: VueWatcher, runCleanup: () => void) {
204202

205203
function createWatcher(
206204
vm: ComponentInstance,
207-
source: WatchSource<unknown> | WatchSource<unknown>[] | WatchEffect,
208-
cb: WatchCallback<any> | null,
205+
source: WatchSource | WatchSource[] | WatchEffect,
206+
cb: WatchCallback | null,
209207
options: WatchOptions
210208
): () => void {
211209
if (__DEV__ && !cb) {
@@ -416,27 +414,47 @@ export function watchSyncEffect(effect: WatchEffect) {
416414
return watchEffect(effect, { flush: 'sync' })
417415
}
418416

419-
// overload #1: array of multiple sources + cb
420-
// Readonly constraint helps the callback to correctly infer value types based
421-
// on position in the source array. Otherwise the values will get a union type
422-
// of all possible value types.
417+
// overload #1 + #2 + #3: array of multiple sources + cb
418+
419+
// overload #1: In readonly case the first overload must be spread tuple type.
420+
// In otherwise members in tuple can not get the correct types.
421+
export function watch<
422+
T extends Readonly<MultiWatchSources>,
423+
Immediate extends Readonly<boolean> = false
424+
>(
425+
sources: [...T],
426+
cb: WatchCallback<MapSources<T, false>, MapSources<T, Immediate>>,
427+
options?: WatchOptions<Immediate>
428+
): WatchStopHandle
429+
430+
// overload #2: for not spread readonly tuple
431+
export function watch<
432+
T extends Readonly<MultiWatchSources>,
433+
Immediate extends Readonly<boolean> = false
434+
>(
435+
sources: T,
436+
cb: WatchCallback<MapSources<T, false>, MapSources<T, Immediate>>,
437+
options?: WatchOptions<Immediate>
438+
): WatchStopHandle
439+
440+
// overload #3: for not readonly multiSources
423441
export function watch<
424-
T extends Readonly<WatchSource<unknown>[]>,
442+
T extends MultiWatchSources,
425443
Immediate extends Readonly<boolean> = false
426444
>(
427445
sources: [...T],
428-
cb: WatchCallback<MapSources<T>, MapOldSources<T, Immediate>>,
446+
cb: WatchCallback<MapSources<T, false>, MapSources<T, Immediate>>,
429447
options?: WatchOptions<Immediate>
430448
): WatchStopHandle
431449

432-
// overload #2: single source + cb
450+
// overload #4: single source + cb
433451
export function watch<T, Immediate extends Readonly<boolean> = false>(
434452
source: WatchSource<T>,
435453
cb: WatchCallback<T, Immediate extends true ? T | undefined : T>,
436454
options?: WatchOptions<Immediate>
437455
): WatchStopHandle
438456

439-
// overload #3: watching reactive object w/ cb
457+
// overload #5: watching reactive object w/ cb
440458
export function watch<
441459
T extends object,
442460
Immediate extends Readonly<boolean> = false

test-dts/watch.test-d.tsx

+33-6
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,44 @@ watch(source, (value, oldValue) => {
1010
expectType<string>(oldValue)
1111
})
1212

13-
watch([source, source2, source3], (values, oldValues) => {
14-
expectType<(string | number)[]>(values)
15-
expectType<(string | number)[]>(oldValues)
16-
})
13+
// spread array
14+
watch(
15+
[source, source2, source3],
16+
([source1, source2, source3], [oldSource1, oldSource2, oldSource3]) => {
17+
expectType<string>(source1)
18+
expectType<string>(source2)
19+
expectType<number>(source3)
20+
expectType<string>(oldSource1)
21+
expectType<string>(oldSource2)
22+
expectType<number>(oldSource3)
23+
}
24+
)
1725

1826
// const array
19-
watch([source, source2, source3], (values, oldValues) => {
27+
watch([source, source2, source3] as const, (values, oldValues) => {
2028
expectType<Readonly<[string, string, number]>>(values)
2129
expectType<Readonly<[string, string, number]>>(oldValues)
30+
expectType<string>(values[0])
31+
expectType<string>(values[1])
32+
expectType<number>(values[2])
33+
expectType<string>(oldValues[0])
34+
expectType<string>(oldValues[1])
35+
expectType<number>(oldValues[2])
2236
})
2337

38+
// const spread array
39+
watch(
40+
[source, source2, source3] as const,
41+
([source1, source2, source3], [oldSource1, oldSource2, oldSource3]) => {
42+
expectType<string>(source1)
43+
expectType<string>(source2)
44+
expectType<number>(source3)
45+
expectType<string>(oldSource1)
46+
expectType<string>(oldSource2)
47+
expectType<number>(oldSource3)
48+
}
49+
)
50+
2451
// immediate watcher's oldValue will be undefined on first run.
2552
watch(
2653
source,
@@ -42,7 +69,7 @@ watch(
4269

4370
// const array
4471
watch(
45-
[source, source2, source3],
72+
[source, source2, source3] as const,
4673
(values, oldValues) => {
4774
expectType<Readonly<[string, string, number]>>(values)
4875
expectType<

test/v3/runtime-core/apiWatch.spec.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ describe('api: watch', () => {
143143
const status = ref(false)
144144

145145
let dummy
146-
watch([() => state.count, status], (vals, oldVals) => {
146+
watch([() => state.count, status] as const, (vals, oldVals) => {
147147
dummy = [vals, oldVals]
148148
const [count] = vals
149149
const [, oldStatus] = oldVals

0 commit comments

Comments
 (0)