expectType<string>(eset1.foo)
expectType<number>(eset1.bar)
})
+
+describe('should not error when assignment', () => {
+ const arr = reactive([''])
+ let record: Record<number, string>
+ record = arr
+ expectType<string>(record[0])
+ let record2: { [key: number]: string }
+ record2 = arr
+ expectType<string>(record2[0])
+})
import {
+ type ComputedRef,
+ type Ref,
computed,
defineComponent,
defineModel,
+ reactive,
ref,
shallowRef,
watch,
const source2 = computed(() => source.value)
const source3 = () => 1
+type Bar = Ref<string> | ComputedRef<string> | (() => number)
+type Foo = readonly [Ref<string>, ComputedRef<string>, () => number]
type OnCleanup = (fn: () => void) => void
+const readonlyArr: Foo = [source, source2, source3]
+
// lazy watcher will have consistent types for oldValue.
watch(source, (value, oldValue, onCleanup) => {
expectType<string>(value)
expectType<Readonly<[string, string, number]>>(oldValues)
})
+// reactive array
+watch(reactive([source, source2, source3]), (value, oldValues) => {
+ expectType<Bar[]>(value)
+ expectType<Bar[]>(oldValues)
+})
+
+// reactive w/ readonly tuple
+watch(reactive([source, source2, source3] as const), (value, oldValues) => {
+ expectType<Foo>(value)
+ expectType<Foo>(oldValues)
+})
+
+// readonly array
+watch(readonlyArr, (values, oldValues) => {
+ expectType<Readonly<[string, string, number]>>(values)
+ expectType<Readonly<[string, string, number]>>(oldValues)
+})
+
+// no type error, case from vueuse
+declare const aAny: any
+watch(aAny, (v, ov) => {})
+watch(aAny, (v, ov) => {}, { immediate: true })
+
// immediate watcher's oldValue will be undefined on first run.
watch(
source,
{ immediate: true },
)
+// reactive array
+watch(
+ reactive([source, source2, source3]),
+ (value, oldVals) => {
+ expectType<Bar[]>(value)
+ expectType<Bar[] | undefined>(oldVals)
+ },
+ { immediate: true },
+)
+
+// reactive w/ readonly tuple
+watch(reactive([source, source2, source3] as const), (value, oldVals) => {
+ expectType<Foo>(value)
+ expectType<Foo | undefined>(oldVals)
+})
+
+// readonly array
+watch(
+ readonlyArr,
+ (values, oldValues) => {
+ expectType<Readonly<[string, string, number]>>(values)
+ expectType<
+ Readonly<[string | undefined, string | undefined, number | undefined]>
+ >(oldValues)
+ },
+ { immediate: true },
+)
+
// should provide correct ref.value inner type to callbacks
const nestedRefSource = ref({
foo: ref(1),
type DeepReadonly,
type ShallowReactive,
type UnwrapNestedRefs,
+ type Reactive,
+ type ReactiveMarker,
} from './reactive'
export {
computed,
// only unwrap nested ref
export type UnwrapNestedRefs<T> = T extends Ref ? T : UnwrapRefSimple<T>
+declare const ReactiveMarkerSymbol: unique symbol
+
+export declare class ReactiveMarker {
+ private [ReactiveMarkerSymbol]?: void
+}
+
+export type Reactive<T> = UnwrapNestedRefs<T> &
+ (T extends readonly any[] ? ReactiveMarker : {})
+
/**
* Returns a reactive proxy of the object.
*
* @param target - The source object.
* @see {@link https://vuejs.org/api/reactivity-core.html#reactive}
*/
-export function reactive<T extends object>(target: T): UnwrapNestedRefs<T>
+export function reactive<T extends object>(target: T): Reactive<T>
export function reactive(target: object) {
// if trying to observe a readonly proxy, return the readonly version.
if (isReadonly(target)) {
type EffectScheduler,
ReactiveEffect,
ReactiveFlags,
+ type ReactiveMarker,
type Ref,
getCurrentScope,
isReactive,
onCleanup: OnCleanup,
) => any
+type MaybeUndefined<T, I> = I extends true ? T | undefined : T
+
type MapSources<T, Immediate> = {
[K in keyof T]: T[K] extends WatchSource<infer V>
- ? Immediate extends true
- ? V | undefined
- : V
+ ? MaybeUndefined<V, Immediate>
: T[K] extends object
- ? Immediate extends true
- ? T[K] | undefined
- : T[K]
+ ? MaybeUndefined<T[K], Immediate>
: never
}
// overload: single source + cb
export function watch<T, Immediate extends Readonly<boolean> = false>(
source: WatchSource<T>,
- cb: WatchCallback<T, Immediate extends true ? T | undefined : T>,
+ cb: WatchCallback<T, MaybeUndefined<T, Immediate>>,
options?: WatchOptions<Immediate>,
): WatchStopHandle
-// overload: array of multiple sources + cb
+// overload: reactive array or tuple of multiple sources + cb
export function watch<
- T extends MultiWatchSources,
+ T extends Readonly<MultiWatchSources>,
Immediate extends Readonly<boolean> = false,
>(
- sources: [...T],
- cb: WatchCallback<MapSources<T, false>, MapSources<T, Immediate>>,
+ sources: readonly [...T] | T,
+ cb: [T] extends [ReactiveMarker]
+ ? WatchCallback<T, MaybeUndefined<T, Immediate>>
+ : WatchCallback<MapSources<T, false>, MapSources<T, Immediate>>,
options?: WatchOptions<Immediate>,
): WatchStopHandle
-// overload: multiple sources w/ `as const`
-// watch([foo, bar] as const, () => {})
-// somehow [...T] breaks when the type is readonly
+// overload: array of multiple sources + cb
export function watch<
- T extends Readonly<MultiWatchSources>,
+ T extends MultiWatchSources,
Immediate extends Readonly<boolean> = false,
>(
- source: T,
+ sources: [...T],
cb: WatchCallback<MapSources<T, false>, MapSources<T, Immediate>>,
options?: WatchOptions<Immediate>,
): WatchStopHandle
Immediate extends Readonly<boolean> = false,
>(
source: T,
- cb: WatchCallback<T, Immediate extends true ? T | undefined : T>,
+ cb: WatchCallback<T, MaybeUndefined<T, Immediate>>,
options?: WatchOptions<Immediate>,
): WatchStopHandle
DebuggerEvent,
DebuggerEventExtraInfo,
Raw,
+ Reactive,
} from '@vue/reactivity'
export type {
WatchEffect,