Generic component enhancements on <script setup>
and on defineComponent
#436
Replies: 30 comments 90 replies
-
This is difficult but it's much needed! One thing you don't mention but should be included is typing slots properties with generic types, see e.g. this comment in the linked discussion. When consuming a generic component, it'd be amazing when the generic types can be inferred, for when it cannot we're gonna need a syntax to indicate explicit types. |
Beta Was this translation helpful? Give feedback.
-
One concern of <script setup lang="ts">
interface MyItem {
name: string;
foo: number
}
// a mock for TypeVar can be
// type TypeVar<Name, Bound = unknown, Default = unkown> = intrinsic
type T = TypeVar<'T', MyItem> // equivalent to T extends MyItem
defineProps<{
list: T[],
modelValue?: T
}>
</script> This solution, with a global type definition, can work in current vanilla TypeScript editor without compiler change. So it should be much easier to implement. I will appreciate @johnsoncodehk 's idea on these alternatives. |
Beta Was this translation helpful? Give feedback.
-
Hi! What about this sample? <script setup lang="ts">
// GerericList.vue
import { onMounted } from 'vue'
interface GenericItem {}
interface GerericListEvents<Item extends GenericItem> {
(event: 'select', item: Item): void
}
interface GerericListProps<Item extends GenericItem> {
items: Item[]
selected?: Item
}
const emit = defineEmits<GerericListEvents<GenericItem>>()
const prop = defineProps<GerericListProps<GenericItem>>()
onMounted(() => {
if (!prop.selected && prop.items.length > 0) {
emit('select', prop.items[0])
}
})
</script> |
Beta Was this translation helpful? Give feedback.
-
+1 , this feature is really needed |
Beta Was this translation helpful? Give feedback.
-
Please don't let this go dead. In my opinion this is the #1 lacking area of Vue as a framework compared to React. |
Beta Was this translation helpful? Give feedback.
-
+1 I hope we will have this feature soon. Generic components are a very crucial part of the TypeScript-friendly framework. Is there anywhere I can track progress or help maybe? |
Beta Was this translation helpful? Give feedback.
-
Volar introduced the generic type for component |
Beta Was this translation helpful? Give feedback.
-
The chaining of Could we still create an API that reads left to right? export default defineGeneric<T extends { id: number }>()
.component({ setup }); |
Beta Was this translation helpful? Give feedback.
-
I would love generic scoped slots. Imagine the following imaginary multiselect component: <script lang="ts">
export class BaseItem {
key: number | string;
label: string;
constructor(key: number | string, label: string) {
this.key = key;
this.label = label;
}
}
export class ExtraDataItem extends BaseItem {
extraData = "Press F5 to pay respects";
}
</script>
<script setup lang="ts" generic="T extends BaseItem">
const props = defineProps<{
items: T[];
selectedItems: T[];
}>();
</script>
<template>
<div>
<div v-for="item in props.items" :key="item.key">
<slot :item="item" />
</div>
</div>
</template> It would be cool if one could see the 'item' passed into the item template as 'ExtraDataItem'. The generic type could be auto inferred based on passing ExtraDataItem arrays as items and selectedItems. |
Beta Was this translation helpful? Give feedback.
-
Personally, I don't think this approach is intuitive. Because this gives me the impression that I used type <script setup lang="ts" generic="T extends MyInterface">
import { MyInterface } from './types'
defineProps<{ types: T }>()
</script> The following are some of my personal thoughts, I have not delved into the core principles and background, just from a developer's perspective. Using a Of course, since there are no operators (For example, This means that all import declarations, type declarations, of this block will be elevated to the top of SFC, and any other block ( In addition, after separation into blocks, <!-- Vue Type Block (Of course you can change the name) -->
<VueTypeBlock>
import { SomeType } from "./types"
interface LocalType {}
// magic
generic: T extends LocalType
generic: U extends SomeType
</VueTypeBlock>
<script setup lang="ts">
// 1. I remember that currently these compiled macros are not available with the imported types.
// 2. Can this be achieved using custom blocks?
// The generic type is from VueGenericBlock
defineProps<T, U>()
defineProps<SomeType>()
</script> |
Beta Was this translation helpful? Give feedback.
-
@yyx990803 can this RFC have some love in the 2023 Vue roadmap? |
Beta Was this translation helpful? Give feedback.
-
place ts code into vue tag[attr] is danger |
Beta Was this translation helpful? Give feedback.
-
In this case, I prefer the solution below 👇 <script setup lang='ts'>
declare module 'ComponentName' {
interface Component<T extends string> {}
}
</script> |
Beta Was this translation helpful? Give feedback.
-
I think there needs to just be a magic keyword. Generic Keyword <script lang="ts" setup>
generic: T, U
</script> Generic Keyword with imports <script lang="ts" setup>
import { Props } from "lib"
generic: T extends Props, U
</script> |
Beta Was this translation helpful? Give feedback.
-
// ./utils.ts
export interface ColumnItem<Data> {
format(item: Data): string
}
// ./Comp.vue
import { ColumnItem } from './utils'
declare function definePropsWithGeneric<Data extends object>(): { records: Data[], columns: ColumnItem<Data>[] }
const props = withDefaults(definePropsWithGeneric(), { records: [] }) |
Beta Was this translation helpful? Give feedback.
-
I had no issues with this for versions older than 3.2.26 (I was originally on 3.2.13 but reverting the version back breaks ref assignment for elements, @VUE:mount also breaks). Now, as soon as I add a prop defined as b:T, or c:key of T, it breaks. It doesn't break if I do T[] though. Example on vue 3.2.47, volar 1.2.0:
I'm getting error for the assignment of default value to b, is not assignable to parameter of type 'InferDefaults...
|
Beta Was this translation helpful? Give feedback.
-
Hello, I used
Has anyone found a solution to this please? For reference versions: |
Beta Was this translation helpful? Give feedback.
-
code [email protected] <script setup lang="ts" generic="K extends keyof KMap">
import Genric from "./Genric.vue";
export interface KMap {
a: "a_1" | "a_2";
b: "b_1" | "b_2";
}
interface Props<K extends keyof KMap> {
name: K;
value?: KMap[Props<K>['name']]
}
defineProps<Props<K>>();
withDefaults(defineProps<Props<K>>(), {
name: "a",
});
</script>
<template>
<Genric name="a" title="t"></Genric>
</template> |
Beta Was this translation helpful? Give feedback.
-
After reading this and other related topics, I still don't understand, is it possible to use component typing through
|
Beta Was this translation helpful? Give feedback.
-
Hi, I have been so happy coding with this new generic feature! Kudos for this to @pikax and all others creating this!
I get 2 typescript errors:
It only happens when I try to export it. As long as I keep it internal there is no problem. |
Beta Was this translation helpful? Give feedback.
-
Not quite sure why it's reporting an error <script setup lang="ts" generic="Data, FidID extends keyof Data">
defineProps<{
data: Data[]
fidId: FidID
}>()
defineSlots<{
default: (props: { row: Data }) => void
}>()
</script>
<template>
<ul>
<li v-for="item of data" :key="item[fidId]">
<slot :row="item">
{{ item[fidId] }}
</slot>
</li>
</ul>
</template> error:
|
Beta Was this translation helpful? Give feedback.
-
@pikax @yyx990803 I'm a little uncertain about the status of this RFC, at this point. It looks like it was effectively "merged" in Vue 3.3, but the status of the actual RFC in this repo is still "Open". Is this feature considered stable? Or no? |
Beta Was this translation helpful? Give feedback.
-
I apologize if this is documented and I haven't been able to find it. I have a situation where I'm defining a generic component, but when it's used the component can't infer its own type (it's getting a value from the ambient |
Beta Was this translation helpful? Give feedback.
-
Did someone experience typing issue with generic component and attribute inheritance? vuejs/core#8372 |
Beta Was this translation helpful? Give feedback.
-
For those ones that want to manually specify generic arguments, you could try the following solutions: <template>
<component :is="GenericComp<string>">
<template v-slot="{ value }">
{{ value }} <!-- Would be string -->
</template>
</component>
<GenericComp1>
<template v-slot="{ value }">
{{ value }} <!-- Would be string -->
</template>
</GenericComp1>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import GenericComp from './GenericComp.vue'
const GenericComp1 = GenericComp<string>
</script>
|
Beta Was this translation helpful? Give feedback.
-
Hey guys. Sorry to bring this up again. I know this RFC is live already and I am enjoying it a lot. I'm just having some issues with generics that I don't quite know if it's something to be reported or i'm just doing it wrong. Basically, I am trying to create the fabled select/multiselect using generics. Here's a link to reproduce this issue. I have the component that defines the select options and also the value to be emitted by the event with the gereric type. The single prop will decide if the emitted value is an object or an array of the object being referenced. <script setup lang="ts" generic="Option extends SelectOption">
export interface SelectOption { ... }
export interface CompProps<TOption> {
options: TOption[]
single?: boolean
modelValue?: TOption | TOption[]
}
defineProps<CompProps<Option>>()
defineEmits<{
(e: 'update:model-value', value: Option | Option[]) : void
}>()
</script> When trying to handle this in a parent component: <script setup lang="ts">
interface CompOption {
id: string | number
name: string
other: string
}
const options = ref<CompOption[]>([ ... ])
function handleSelect(value: CompOption) {
selectedValue.value = value
}
</script>
<template>
<Comp
:options="options"
single
@update:model-value="handleSelect"
/>
</template> I know for fact that the event should emit a single object as its value, but I get this ts error saying otherwise. How would I be able to make this work and tell typescript that the handleSelect function is actually receiving the correct value? |
Beta Was this translation helpful? Give feedback.
-
@yyx990803 generic components are fantastic ❤️ With the addition of TS annotation inside templates, generic components, and typed slots we have finally been able to type everything properly! It was work to fix the large codebase (and a few generic controls have crazy types) but now there are 0 errors and we have strict type validation and completion everywhere. Vue DX with Volar is top-class, a few years ago I would never have dreamt of such experience for front-end dev! Congrats on your 2023 achievements, and I know there is more greatness coming in 2024 (Vapor, even better TS in Volar, etc.)! |
Beta Was this translation helpful? Give feedback.
-
So how can one pass the type to the component when used inside a template? I have a component with generic type |
Beta Was this translation helpful? Give feedback.
-
I'm having an error when importing a interface that has generic typing. This is the error.
The weird behavior is that everything is working fine when this interface is declared in the .vue file where it is being consume.
|
Beta Was this translation helpful? Give feedback.
-
@pikax |
Beta Was this translation helpful? Give feedback.
-
Summary
Allow to infer generics when passing props on the
<template>
,JSX
orh
.This will only affect typings used by typescript (IDE, TSX, etc) and don't bring any runtime cost.
There's 2 use cases:
This RFC will target both User and IDE, distinction will be explicit
Basic example
Script setup - IDE
Simple
T
With
extends
Multiple (from vuejs/core#3102)
with imported types +
extends
DefineComponent - User
This is not user friendly and is more intended to be used for library creators
Simple
T
Multiple (from vuejs/core#3102)
Motivation
Provide a way to support
Generics
, since Vue3 has first class support for Typescript the only place it lacks is allowing developers to clearly express the types the component expects, instead of relying on catch all types (eg: Object, Array, null, etc).The usage with
defineComponent
is not great but it works today (using Volar), because of the requirement of having a constructor that has a generic, there's no other way (that I can think of) to solve this in a cleaner way.I expect the usage with
script setup
will bring this feature to be used more, since the API is simple and we have already the step of compiling it.Detailed design
To be able to understand this RFC we first need to understand that Types (typescript) and Implementation(javascript), might need to be handle differently, sometimes we need to patch the
defineComponent
for it to reflect a more accurate component.Script Setup - IDE
IDE extensions (eg: Volar), will require to do heavy lifting here, the design will be provided in TSX since Vue3 already supports first class and it should be supported today - if you doubt go to typescript playground and paste the results there.
API
This will add a new attribute named
generic
, inside of that argument is a valid typescript language and ideally we should have intellisense.generic
value is basically the content insideclass MyClass<${genericValue}>
.Arguments specified inside of
generic
will be available and have intellisense inside of thescript setup
.This conversions are high-level and simplified, but still be valid on typescript only environment
Typescript to component - NOT GENERIC COMPONENT
Converted to:
Generic Components
Converting
script setup
to typescript compatible code won't make your component be able to infer the types when used on the<template>
, you will only get statically typed components (Generically Typed Vue.js Components)Simple
T
SFC
Result playground
extends type
SFC
Result playground
DefineComponent
Is not required any further work on IDE, the only work needed is to expose this types on Vue3 package:
Playground
Drawbacks
Script Setup
This is the way with less drawbacks, DX should be great and it will be the preferred way.
defineComponent
This brings quite a lot of work and DX is not amazing, this will require the user to be quite strict to prevent wrong type inferences, because props will be duplicated, errors are easy to show up.
Alternatives
defineComponent
changes can be already be done on theuserLand
, but it would be good to provide guidance on how to do this, I expect libraries to take advantage of this to provide better type for their components#310 - Class alternative
Adoption strategy
This functionality is additive, we should only update the components that are actually generic.
Unresolved questions
Naming suggestions or improvements on the API are welcome.
Beta Was this translation helpful? Give feedback.
All reactions