Subscribe on changes!

typing: ref of type A | undefined do not unwrap refs in A

avatar
Dec 13th 2020

Version

3.0.4

Reproduction link

https://codesandbox.io/s/goofy-sinoussi-gojxu?file=/src/index.ts

Steps to reproduce

const r = ref<{ a: Ref<number> } | undefined>({ a: ref(3) });
r.value.a // should be type `number`, but shown as `Ref<number>`

What is expected?

r.value.a should be type number

What is actually happening?

r.value.a is shown as Ref<number> instead.

avatar
Dec 14th 2020

It is because of the override function.

export function ref<T extends object>(value: T): ToRef<T>
export function ref<T>(value: T): Ref<UnwrapRef<T>>
export function ref<T = any>(): Ref<T | undefined>

your code const x = ref<{ a: Ref<number> } | undefined>(); will run as ref<T = any>(): Ref<T | undefined>. The T type doesn't include in the UnwrapRef. So, the export function ref<T = any>(): Ref<T | undefined> should be export function ref<T = any>(): Ref<UnwrapRef<T> | undefined>.

avatar
Dec 14th 2020

But while changing export function ref<T = any>(): Ref<T | undefined> to export function ref<T = any>(): Ref<UnwrapRef<T> | undefined>, ref's setter shows the error setting type.

const x = ref<{ a: Ref<number> } | undefined>();
// ERROR Type 'Ref<number>' is not assignable to type 'number'.ts(2322)
x.value = { a: ref(1) };
avatar
Dec 14th 2020

Make sense. Thanks you @Zcating.

I find it slightly hard to work with the typing when we have nested refs.

For example, this will cause the type go wrong

const useA = () => ({
  a: ref(3),
});
const x = ref(useA());
x.value = useA(); // you can't do this coz x is not `Ref<{ a: number }>`
avatar
Dec 14th 2020

It is because the getter & setter must be the same type. If change ref.value to ref.setValue & ref.getValue, you can do this:

setValue(value: T);
getValue(): UnwarpRef<T>;
avatar
Dec 19th 2020

The typing is correct. If you want it to be a number, you should do

const r = ref<{ a: number } | undefined>({ a: 3 });