Component $refs type should be more narrow
Vue version
3.2.37
Link to minimal reproduction
https://stackblitz.com/edit/vitejs-vite-fpuqmq?file=package.json
Steps to reproduce
Build a component using defineComponent, typescript and the options api with at template ref, then try to use that template ref.
What is expected?
The type of that template ref will be something reasonable like undefined | HTMLElement
or undefined | HTMLElement | DefineComponent
or any
instead of unknown.
What is actually happening?
$refs
is a Record<string, unknown>
, forcing a cast whenever using it.
System Info
No response
Any additional comments?
No response
With the proposed type you would also be forced to cast it pretty much whenever using it, wouldn't you?
- want to access a property of an HTMLElement? property does not exist on
DefineComponent
-> need to cast - want to access a component API method, like $emit? property does not exist on
HTMLElement
-> need to cast - want to access a property of a specific component? property does not exist on
HTMLElement
norDefineComponent
-> need to cast
Sure, we could use any
, but TS best practices usually recommend against so implicitly weak APIs, leading people to write code that is prone to typo errors and other mistakes TS is usually meant to protect you from.
Tip: even if you prefer Options API: use composition API just for template refs:
export defineComponent() {
setup() {
return { el: ref<HTMLElement>() }
},{
mounted() {
this.el // => HTMLElment | undefined
}
}
Yes, but would better inform the user what is going in in the situation. If I see "Property 'innerText' does not exist on type 'DefineComponent | HTMLElement': Property 'innerText' does not exist on type 'DefineComponent'." I'll immediate realize what the problem is and what I need to cast too.
"Object is of type 'unknown'." Provides no information about what the issue is and what the fix is. Most people are probably just going to cast it to any
anyhow. Others who like the type safety but aren't experts are going to have to google to figure out what the type of an HTMLElement. The truly neurotic might even end up bugging people on github about it.
It also allows someone to write better type guards to sort out the difference, should someone end up in the unlikely situation of need to us the template ref but not knowing what it is.
Also realizing it should probably be Element
instead of HTMLElement
, can't forget about SVGElement.
I see your point. It could be a worthwhile enhancement, but I'd be worried that it might break existing code in userland now, as it's not guaranteed that the typecasting people do now would be compatible with HTMLElement | DefineComponent
. People might be doing this right now:
(this.$refs.myComponent as { theOneProperyImInterestedIn: string }).theOnePropertyImInterestedIn = 'new value'
which, with your proposed type, would now break. People would then have to do:
(this.$refs.myComponent as unknown as { theOneProperyImInterestedIn: string }).theOnePropertyImInterestedIn = 'new value'
So technically, this is a breaking change.
Also, we would have to create this type in a way that it was extendable as Element
would only make sense in the context of runtime-dom
, while ptojects like troisjs provide their own renderer, in which refs could point to things such as a ThreeJS scene instance.