Nested `ref` has invalid typing
Vue version
3.3.11
Link to minimal reproduction
Steps to reproduce
Just nest some refs
, it works for first level, but doesn't work on second nested ref
What is expected?
It's expected to unwrap the second nested ref as well, it works that way in runtime, therefor the typing is wrong
What is actually happening?
it expexts to write .value
, which is wrong
System Info
No response
Any additional comments?
I've done some changes in code in ref.ts
:
export type ShallowRef<T = any> = Ref<T> & { [ShallowRefMarker]: true } // remove the `?:`
export function shallowRef<T>(value: MaybeRef<T>): ShallowRef<T> // remove the `: Ref<T> | ShallowRef<T>
and wrote a test:
describe('unwraps nested refs', () => {
const x = ref({
a: ref({
b: ref(0)
})
})
expectType<number>(x.value.a.b)
})
and works fine now. It seems like UnwrapRef
treats second nested ref
as shallowRef
, due to ShallowRefMarker
not being false (it extends ShallowRef, because [ShallowRef] is undefined, that's why removing the ?:
fixed it here)..
What is the expected behaviour here: the runtime, or type?
The runtime type
and wrote a test:
describe('unwraps nested refs', () => { const x = ref({ a: ref({ b: ref(0) }) }) expectType<number>(x.value.a.b) })
That type is already correct without any changes to the current code, please confirm on playground
What is the expected behaviour here: the runtime, or type?
The runtime type is correct, on your type declaration you not unwrapping the Ref, making the type a Ref, that's intended, since by default Refs are already unwrapped by default when used correctly.
When doing type, you should add the unwrapped ref manually, see
import { Ref, UnwrapRef } from 'vue'
type Foo = {
foo: UnwrapRef<Ref<{
bar: UnwrapRef<Ref<string>>
}>>
}
type Unwrapped = UnwrapRef<Foo>
// ^?
declare const a: Unwrapped
a.foo.bar = ''
// @ts-expect-error
a.foo.bar = 1
I'll close this issue, since is not an issue.
It seems to work fine as you say with literal types, but type transformations don't work as fine. I guess I didn't do too much tests in Vue's source codes, but spent a lot of time trying to understand and figure out how to type the nested Refs, instead of just using generated types from 'literals':
type Item = {
id: number;
title: string;
nested: UnwrapRef<Ref<Item | null>>;
};
const item = ref<Item>({
id: 1,
title: "foo",
nested: ref(null),
});
Do you know how to create a ref of such type easily? It says Type 'Ref<null>' is missing the following properties from type 'Item': id, title, nested(2739)
. Why does it here require null
to own those fields, if it's just one of the possibilities?
I'd like an example of real usage of such types. I can't find a way to work with that using actual code, the type transformations here seem to be messing it up a bit too much.
playground here I'd like the pointer to show it's not Ref
, but actual nested Item | null
.
The types are working as intended, the issue is the complexity of it and some of typescript limitations that makes it quite confusing.
The rule for the Ref
is, the generic type of T
in Ref<T>
is already unwrapped by default when used correctly in ref({} as MyType)
, meaning when we unwrap the type we check if is a Ref<infer InnerT>
and then return the innerT
, innerT
is already implicitly UnwrapRef<InnerT>
, but in you case you're still declaring the innerT
as having Ref
: nested: UnwrapRef<Ref<Item | null>>;
, making the extract to be Item | null
since Item
has a nested ref, that won't be unwrapped, the correct type would be Ref<UnwrapRef<Item> | null>
in your example.
I'd like an example of real usage of such types. I can't find a way to work with that using actual code, the type transformations here seem to be messing it up a bit too much.
Most of the usage should be handled automatically by the literal types when using the functions, when you start adding Type manipulation the complexity grows exponentially, without a good understanding of how it works I admit it might be confusing, maybe this would be something to be added to the docs, I'm currently working on adding advance typescript docs for the v3.5+, might be useful to add some of type manipulation examples there.
Do you know how to create a ref of such type easily? It says
Type 'Ref<null>' is missing the following properties from type 'Item': id, title, nested(2739)
. Why does it here requirenull
to own those fields, if it's just one of the possibilities?
That type is UnwrapRef<Ref<Item | null>>;
which is not the same as Ref<null>
the correct type here would be UnwrapRef<Ref<null>>
, if you remove the UnwrapRef
from your example it able to infer the correct type, you can also only Ref<UnwrapRef<Item> | null>
, which would be the correct type here.
I'd like an example of real usage of such types. I can't find a way to work with that using actual code, the type transformations here seem to be messing it up a bit too much.
If you give me some example that you think are complicated to do, please feel free to let me know, I can help you here and will also give me some ideas to add to the docs.
If you prefer using the types like that, you can also create an helper type to patch those refs
:
type PatchRef<T> = {
[K in keyof T]: T[K] extends Ref<infer InnerT> ? Ref<UnwrapRef<InnerT>> ? T[K]
}
That is not possible to be done to the core
because the type mutations like that are undesirable because there's no short circuit to prevent UnwrapRef
from happening, that's a big issue for Generic Types.