From 288e094e1e41e6d3c0e39d0144e3dd7374e606d3 Mon Sep 17 00:00:00 2001 From: tycho Date: Wed, 7 Aug 2024 20:18:52 +0800 Subject: [PATCH 1/2] fix(types/ref): correct type inference for generics w/ `Ref` when omitting initial value --- packages-private/dts-test/ref.test-d.ts | 31 +++++++++++++++++++++++++ packages/reactivity/src/ref.ts | 9 ++++++- packages/shared/src/typeUtils.ts | 7 ++++++ 3 files changed, 46 insertions(+), 1 deletion(-) diff --git a/packages-private/dts-test/ref.test-d.ts b/packages-private/dts-test/ref.test-d.ts index 929e3d1a1..7d73e4def 100644 --- a/packages-private/dts-test/ref.test-d.ts +++ b/packages-private/dts-test/ref.test-d.ts @@ -217,6 +217,37 @@ describe('Type safety for `WritableComputedRef` and `ComputedRef`', () => { expectType>(immutableComputed) }) +declare class Foo { + private _: number +} + +describe('omit the initial value and specify a generic w/ `Ref` type', () => { + const a = ref<{ foo: Ref }>() + expectType<{ foo: number } | undefined>(a.value) + expectType(a.value!.foo) + a.value = undefined + a.value = { foo: ref(1) } + a.value!.foo = 2 + // @ts-expect-error + a.value.foo.value = 2 + + const b = ref>() + expectType | undefined>(b.value) + expectType(b.value!.value) + b.value = undefined + b.value = ref(1) + b.value!.value = 1 + + const c = ref() + c.value = undefined + c.value = {} as T + + const d = ref() + expectType>(d) + d.value = new Foo() + expectType(d.value) +}) + // shallowRef type Status = 'initial' | 'ready' | 'invalidating' const shallowStatus = shallowRef('initial') diff --git a/packages/reactivity/src/ref.ts b/packages/reactivity/src/ref.ts index c3104c055..aaec5b042 100644 --- a/packages/reactivity/src/ref.ts +++ b/packages/reactivity/src/ref.ts @@ -1,5 +1,6 @@ import { type IfAny, + type PublicOf, hasChanged, isArray, isFunction, @@ -55,7 +56,13 @@ export function isRef(r: any): r is Ref { export function ref( value: T, ): [T] extends [Ref] ? IfAny, T> : Ref, UnwrapRef | T> -export function ref(): Ref + +export function ref(): [T] extends [Ref] + ? Ref + : PublicOf extends T + ? Ref | undefined, UnwrapRef | T | undefined> + : Ref + export function ref(value?: unknown) { return createRef(value, false) } diff --git a/packages/shared/src/typeUtils.ts b/packages/shared/src/typeUtils.ts index 4846751b8..0449cab89 100644 --- a/packages/shared/src/typeUtils.ts +++ b/packages/shared/src/typeUtils.ts @@ -30,6 +30,13 @@ export type OverloadParameters any> = Parameters< OverloadUnion > +/** + * Utility for strip out `private` / `protected` fields + */ +export type PublicOf = { + [P in keyof T]: T[P] +} + type OverloadProps = Pick type OverloadUnionRecursive< From f4d385177dc7179d5e9a085b97fd65f50bc01e6e Mon Sep 17 00:00:00 2001 From: tycho Date: Thu, 8 Aug 2024 10:25:32 +0800 Subject: [PATCH 2/2] chore: add comment --- packages-private/dts-test/ref.test-d.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages-private/dts-test/ref.test-d.ts b/packages-private/dts-test/ref.test-d.ts index 7d73e4def..196d892ce 100644 --- a/packages-private/dts-test/ref.test-d.ts +++ b/packages-private/dts-test/ref.test-d.ts @@ -218,7 +218,7 @@ describe('Type safety for `WritableComputedRef` and `ComputedRef`', () => { }) declare class Foo { - private _: number + private _: unknown } describe('omit the initial value and specify a generic w/ `Ref` type', () => { @@ -242,6 +242,7 @@ describe('omit the initial value and specify a generic w/ `Ref` type', () => c.value = undefined c.value = {} as T + // case from vueuse const d = ref() expectType>(d) d.value = new Foo()