Replies: 4 comments 1 reply
-
I think this problem lies more on the typescript itself. Typescript don't support generic types as generic type input very well currently. It means such usage is illegal to typescript. type DefineComp<T> = { b: T }
type CompProto<T1> = { c: T1 }
// c is now
// type Comp<T1> = { b: { c: T1 } }
type Comp = DefineComp<CompProto> But that is required if you want to return a generic type value from a function. |
Beta Was this translation helpful? Give feedback.
-
Turns out there's a PR for this already, but it does require you to create an interface for the props: vuejs/core#3682 |
Beta Was this translation helpful? Give feedback.
-
@mmis1000 yes, that's partly why I said the design of this will be challenging. Typescript has very limited support for functions that are generic in generic types, the relevant issue I believe is microsoft/TypeScript#1213 This is even worse if you consider that ideally we may want components that are generic in multiple parameters (definitely useful, there's no reason not to). Maybe Vue can side-step this issue by not making the I mean maybe @KaelWD that's interesting! It's based on classes if I read (quickly) correctly, so that's not a global solution but personally I would be fine using classes for components that have complex typings. I thought classes were completely ruled out of Vue 3, weren't they? If you want to type-check your views, I feel like this is a critical point. Right now my views are littered with false errors that basically are impossible to fix unless I make everything |
Beta Was this translation helpful? Give feedback.
-
I'd like to contribute a real-world example I encountered recently, which combines the needs for generic components and slot types, and can be relatively common. Vuelidate has a <validate-each
v-for="order of basket"
:state="order"
:rules="orderRules"
v-slot="{ v }"
/>
<label>Product: <input v-model="v.product.$model" /></label>
<label>Quantity: <input v-model="v.qty.$model" /></label>
</validate-each> In this example you'd like to validate (and get IDE completion) that To achieve that we would need both:
|
Beta Was this translation helpful? Give feedback.
-
Context
It seems Vue is moving towards having statically typed templates (if you use Typescript, of course), which is awesome for large projects.
Volar provides type feedback in IDE; the default Vite template for
vue-ts
includesvue-tsc
(based on Volar) in its build command; and Vuedx offers similar tools.While trying those out on a real, complex project, I found out that some components can't be used in a type-safe way without being made generic.
A problematic component
As an example, imagine you want to create a
<List>
component for your app that encapsulate some specific behaviors such as look-n-feel, providing multiple selection, some default commands.Say that in order to work properly, your list needs an
id: number
on its items.Authoring the component is no big deal, just declare a
items
prop of type{ id: number }[]
and everything is good and strongly-typed.The problem arises when you try to use this component.
A prop binding is an assignment, and assignments are invariant (as in type theory: co/contra/bi-variant). This means that you can't assign a
Car[]
to{ id: number }[]
, even ifCar
does have anid: number
.This TS constraint makes perfect sense, as the component could mutate
items
and put aPlane
into that array, which would violate type safety.In regular TS code, the only way to make such an assignment work is to declare a
List<TItem extends { id: number }>
, make the propsitems: TItem[]
, and then instantiating aList<Car>
component, which would be perfectly typed.Further uses of generics
Having generic components would enable more precise events and slots types as well (typed slots is being discussed in #192).
Consider the same
List<TItem>
as previously.Give this component a
remove
event, that passes the removed event as a parameter.A generic component can simpy define the event as
remove(item: TItem)
.The best a non-generic component could do is
remove(item: { id: number })
.Note that this signature is not compatible with listener
handleRemove(car: Car)
. Consuming code needs to bind a listener declared ascar: { id: number }
and then use a cast, which is not the best experience and sacrifices some static type safety.That
List<TItem>
certainly has a slot to render its items. Ideally the slot would be typed asTItem
so that the slot template can be fully type-checked (and provide hints when authored).This is again something that is impossible without generics, and would at least require a type annotation (note that TS syntax is currently forbidden in templates) or giving up type safety and IDE completion completely (e.g. if the slot is typed as
any
).Syntax?
I have no great ideas/proposal to enable generic components, I think it's gonna be a little challenging to design.
Currently,
defineComponent
accepts a function and you can writedefineComponent(<T>() => { ... })
, which makes the component code generic but I don't think thatT
is visible to tooling.A specific syntax would need to be defined for
<script setup>
as well.On the consuming side, I don't think vue-compiler will be able to infer generic types in all situations (if at all), so yet another syntax is needed, maybe
v-of
or similar?<List v-of="Car" />
Beta Was this translation helpful? Give feedback.
All reactions