Open dummdidumm opened 4 years ago
Why not use 1 interface for props/events/slots ? This way we can create events, that depends on props:
interface ComponentDefinition<T extends Record<string, string>> {
props: { a: T },
events: { b: T }
}
p.s. Sorry for bad english, tried my best.
Yes this would be possible through the ComponentDef interface
For the slots, props and events, I would lose the Component
prefix:
ComponentSlots
-> Slots
ComponentProps
-> Props
ComponentEvents
-> Events
Then finally ComponentDef
-> Component
.
Doesn't make the names a lot more ambiguous or prone to conflict with existing types I think.
Perhaps separating the type definition from the logic of a component would work nicely.
<script lang="ts" definition>
interface Component<T> {
props: { value: T }
events: {
change: (value: T) => void
}
slots: {
default: {}
}
}
</script>
<script lang="ts">
export let value
</script>
<some>
<slot>Markup</slot>
</some>
I like the shortening of the names although I think this might increase the possibility of name collisions. I'm against putting this into a separate script. This would require more effort in preprocessing, also I like the colocation of the interfaces within the instance script.
Can't wait to use this <3
🙏
Could you elaborate on your second point a little more? I'm don't fully understand the use case. And what do you mean by "impossible"? Possible to do in Svelte but the type checker complains?
(unless I missed something)
you can't pass a component instance to a slot so people end up either
<ParentComponent renderHeader={[SomeSvelteComponent, Props]} />
but today, we have no way to express that Props
should be SomeSvelteComponent's Props beside triple checking manually.
Stumbled upon this and just wanted to throw here a slight variation of Option 2:
<script lang="ts">
import {createEventDispatcher} from "svelte";
type T = ComponentGeneric<boolean>; // extends boolean
type X = ComponentGeneric; // any
export let array1: T[];
export let item1: T;
export let array2: X[];
const dispatch = createEventDispatcher<{arrayItemClick: X}>();
</script>
I think it would be slightly closer to TypeScript code than a ComponentGenerics
interface that gets magically expanded :smile:
Hi !
Any idea when Generic Component will be available/released? Is it perhaps a case of choice paralysis? I think we would all love something even if it's not 100% perfect!!
I'm working on a SvelteTS project for several weeks now, and I would have used this feature a few times already. Btw, love the work you're doing to make SvelteTS a thing. TS support was the thing that made me switch from React to Svelte. 🤗
@tomblachut tagging you since you are the maintainer of the IntelliJ Svelte Plugin - anything in that proposal that concerns you implementation-wise ("not possible to implement on our end")?
@dummdidumm thank you for tagging me.
Generics will definitely be, as you've written, an uncanny valley and maintenance burden.
Option 1 & 2 without Svelte support in editor will produce invalid TS, given that both will require special reference resolution. I think it's better to avoid that.
I'd scratch Option 2, because ComponentGenerics is in the same scope as things that will refer to its type parameters. I imagine it will add some implementation complexity AND mental overhead for users.
I quite like Option 3 because it's valid TS. ComponentGeneric would be treated as identity mapped type.
type T = ComponentGeneric<boolean>; // extends boolean
type X = ComponentGeneric; // any
Option 3 could be even simplified a bit by giving new semantics to export type
in similar way as export let
denotes a prop
export type T = boolean;
export type X = any;
Now, I think it's better to stick to one style of declarations: (separate interfaces/compound ComponentDef/namespace) otherwise we may introduce small bugs in one of them and more importantly will need to decide on and teach about precedence.
One additional thing this proposal does not mention is ability to extend interfaces. I think that's great feature. Author of the component could say "this "PromotedPost adheres to Post props" and whenever types are changed in Post definition, implementing components would show type errors. Unless I'm missing something interfaces will support that use case out of the box.
Thanks for your insights!
I agree that we should only provide one style of declarations. Separate interfaces feels like the best option there.
I also agree that for generics option 3 feels the closest to vanilla TypeScript which is why I prefer that, too. That simplification does not feel quite right for me though, because we are not exporting that generic to anyone, we are just stating that the component is generic. Option 3 has one little shortcoming though, being not being strict enough. Take this code snippet:
type T = ComponentGeneric<{a: boolean}>;
const t: T = {a: true};
Without extra Svelte-specific typing-work, this snippet would not error, because TS does not think of T
as a generic. If it did, it would error with Type '{ a: true; }' is not assignable to type 'T'. '{ a: true; }' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint '{ a: boolean; }'
. In general, errors related to T
would not be in the context of generics. I'd say this is a hit we can take though, because the types are "good enough" for most use cases, and with some extra transformation work (like svelte2tsx
) you can even make TS think that this is a generic - and also it's just not possible to make TS think of T
as a generic at the top level without transformations.
One thing you brought up about extending interfaces is a very good advantage, but it also got me thinking how to deal with generics in that context.
For example you have this interface:
export interface ListProps<ListElement> {
list: ListElement[];
}
How do you extend it while keeping it generic in the context of a Svelte component? The only possibility that comes to my mind is to do this:
<script lang="ts">
import type { ListProps } from '..';
type BooleanListElement = ComponentGeneric<boolean>;
interface ComponentProps extends ListProps<BooleanListElement> {}
export let list: BooleanListElement[];
</script>
..
I miss generics too. I'll leave one example which I would really appreciate to use generics with:
This checkbox selector returns subset of an array of objects without mutating them, which is really handy, but it will return any[]
instead of passing types forward, which is sadge... :disappointed:
<script>
import Checkbox from '../components/Checkbox.svelte';
export let checkboxes = [];
export let checked = [];
export let idF = 'id';
export let textF = 'text';
let checkedIds = new Set(checked.map((c) => c[idF]));
function mark(id) {
checkedIds = new Set(
checkedIds.has(id)
? [...checkedIds].filter((cid) => cid !== id)
: [...checkedIds, id]
);
}
$: checked = checkboxes.filter((c) => checkedIds.has(c[idF]));
</script>
<ul class="checkboxes">
{#each checkboxes as checkbox (checkbox[idF])}
<li>
<Checkbox
checked={checkedIds.has(checkbox[idF])}
on:change={() => mark(checkbox[idF])}
desc={checkbox[textF]}
/>
</li>
{/each}
</ul>
This is proposed way to use generics? I don't think I understood examples correctly, so I added type definitions to this component.
<script lang="ts">
import Checkbox from '../components/Checkbox.svelte';
type Item = ComponentGeneric; // how this will attach to the checkboxes prop ?
export let checkboxes: Item[] = [];
export let checked: Item[] = [];
export let idF = 'id';
export let textF = 'text';
let checkedIds = new Set<number>(checked.map((c: Item) => c[idF]));
function mark(id: number) {
checkedIds = new Set(
checkedIds.has(id)
? [...checkedIds].filter((cid: number) => cid !== id)
: [...checkedIds, id]
):
}
$: checked = checkboxes.filter((c: Item) => checkedIds.has(c[idF]));
</script>
Everything inside this proposal is type-only, which means it's only there to assist you at compile time to find errors early - nothing of this proposal will be usable at runtime, similar to how all TypeScript types will be gone at runtime.
In your example you would do:
<script lang="ts">
// ..
type Item = ComponentGeneric;
type ItemKey = ComponentGeneric<keyof Item>;
export let checkboxes: Item[] = [];
export let checked: Item[] = [];
// Note: For the following props, the default value is left out because you cannot expect "id" or "text" to be present as a default if you don't narrow the Item type
export let idF: ItemKey;
export let textF: ItemKey;
// ..
Wow, keyof is cool.
I want to make sure that I understand correctly, so here's another example:
<script lang="ts">
interface Vegetables {
id: number;
name: string;
weight: number;
}
let someItems: Vegetables[] = [
...
];
let checked: Vegetables[] = [];
</script>
<CheckboxSelector checkboxes={someItems} textF="name" idF="id" bind:checked />
<!-- ^ ^
| | Could it attach to this?
|
| how do I specify that ComponentGeneric
should attach to this?
-->
For example here's how I would use this in regular typescript, which is clear to me.
function component<T>(checkboxes: T[] = [], idF: <keyof T>, textF: <keyof T>) {
...
}
When using the component, you don't specify anything, you just use the component and the types should be inferred and errors should be thrown if the relationship between is incorrect. So in your example if you do textF="nope"
it would error that nope
is not a key of Vegetables
.
Doing
type T = ComponentGeneric;
export let checked: T[];
export let idF: keyof T;
// ...
Would be in the context of Svelte components semantically the same as
function component<T>(checked: T[], idF: keyof T) {
...
}
This is the uncanny valley I'm talking about in the RFC which I fear is unavoidable - it doesn't feel the same like generics, yet it serves this purpose inside Svelte components (and inside only Svelte components). The problem is that Svelte's nice syntax of just directly starting with the implementation without needing something like a wrapping function, so there is no place to nicely attach generics.
So if I end up in a situation where I need two generics:
function component<X, Y>(items: X[], someOtherProp: Y) {
...
}
How that would look in the proposed approach? :thinking: Is it even possible ?
Do you know how bindings will work in the current state?
In the example above I added annotation that checked
in parent component is of same type as someItems
, but if we pass someItems
through checkboxes
prop, we should lose the type, because we can only use any as 'generic' type now in the CheckboxSelector
component, right ?
Do annotations to a bind override any[]
in this case ? :thinking:
Sorry for the wording...
So if I end up in a situation where I need two generics:
function component<X, Y>(items: X[], someOtherProp: Y) { ... }
How that would look in the proposed approach? 🤔 Is it even possible ?
You do this
type X = ComponentGeneric;
type Y = ComponentGeneric;
export let items: X[];
export let someOtherProp: Y;
Do you know how bindings will work in the current state?
In the example above I added annotation that
checked
in parent component is of same type assomeItems
, but if we passsomeItems
throughcheckboxes
prop, we should lose the type, because we can only use any as 'generic' type now in theCheckboxSelector
component, right ?Do annotations to a bind override
any[]
in this case ? 🤔Sorry for the wording...
Sorry, I don't know what you mean.
Sorry, I don't know what you mean.
I hope this is a better way to explain. :thinking:
<script lang="ts">
interface Vegetables {
id: number;
name: string;
weight: number;
}
let someItems: Vegetables[] = [
...
];
// I annotate same type to the checked prop, which I will bind below
// What type checked will have after the bind ?
let checked: Vegetables[] = [];
// I need to use checked in other places and want it to retain the type
</script>
<CheckboxSelector checkboxes={someItems} textF="name" idF="id" bind:checked />
<!-- ^
binding checked here
-->
<script lang="ts">
// ...
// I set any here because I want to accept any object type
// and generics is currently not supported
export let checkboxes: any[] = [];
// this prop will contain a subset of checkboxes prop,
// which I bind above
export let checked: any[] = [];
// ...
</script>
To me this sounds like you mix up some of TypeScript's type system with regular JavaScript. For example let checked = Vegetables[]
is not valid TypeScript, because Vegetables
is an interface, which does not exist at runtime. It should be let checked: Vegetable[]
, which also answers your second question: The type will stay Vegetable
because you told TypeScript that it's of this type, that does not change. Instead, TypeScript would a compiler error if you would try to assign something to checked
that does not suffice the Vegetable
interface (any
is okay, because you can assign any
to anything).
To me this sounds like you mix up some of TypeScript's type system with regular JavaScript.
I'm just making mistakes, not used to type let something: type[] = [];
:grin:
Thanks for the explanation. That's better than nothing.
So looks like the most negatively affected use cases without generics are:
svelte-viewpoint
?
Generics come in handy if you want connect types of 2 or more things to each other. Fo example you may want to guarantee that some event's payload will be of the same type as a prop, or some derivative of that type.
@dummdidumm
Option 3 has one little shortcoming though, being not being strict enough.
Ouch, TIL. Thanks for catching that. Actually not "TIL", I spend couple of days pondering about that 😅 I agree that it's a hit we can take since this problem has additive solution. We can wire up annotations for this on top of normal TS, in contrast to suppressing some false-negatives. 👍
export
has one advantage over ComponentGeneric
- it's not a reference. Though, if it's okay to enable import { ComponentGeneric } from 'svelte';
I'm okay with both approaches. I'd just prefer to avoid global implicit type.
<script lang="ts">
import type { ListProps } from '..';
type BooleanListElement = ComponentGeneric<boolean>;
interface ComponentProps extends ListProps<BooleanListElement> {}
export let list: BooleanListElement[];
</script>
I think your example is elegant.
I didn't consider yet how extends will integrate with SvelteComponentTyped
. Those 3 interfaces would need to be translated into generics of that class. Will it even work?
Implicit global type - I'm a little split on this. There are some other globals already after all, on the other side it's more explicit. Either way, its presence would need extra tooling code (that "it's not the same in TS" thing I mentioned earlier).
About adding it to SvelteComponentTyped
: Do you mean from a tooling perspective how to automatically generate type definitions for libraries, or how to generate the code for the IDE? Either way, some additional logic would be necessary to first extract the interface out to the top level so they can be used on the class, and then another step where the ComponentGeneric
usages are collected and added in order to SvelteComponentTyped
like export class MyComponent<T, X extends string> extends SvelteComponentTyped<{ prop: T },..
. Interfaces could be extracted the same, so that
type T = ComponentGeneric;
interface ComponentEvents {
foo: CustomEvent<T>
}
Becomes
interface ComponentEvents<T> ...
export class MyComponent<T> extends <.. ComponentEvents<T>..
I understand wanting to use as little extra grammar as possible, but I think we should consider using export
to signify a generic type and for specifying the interface of props, slots, and events.
This has a few benefits:
<script lang="ts">
import {createEventDispatcher} from "svelte";
// required generic type extending boolean
export type T extends boolean;
// optional generic type
// defaults to string
export type X = string;
// instead of ComponentSlots
export interface Slots {
default: { aSlot: T }
}
export let array1: T[];
export let item1: T;
export let array2: X[];
const dispatch = createEventDispatcher<{arrayItemClick: X}>();
</script>
The only thing in the example that isn't valid typescript syntax is export type T extends boolean
, which IMO is not asking for much.
Adding invalid TS syntax is a no-go for me, it would require every IDE to know of this and people need to get used to that for Svelte only. Moreover, there wouldn't be things like optional generics. Generics need to be driven by the prop input.
About the exports: I'm hesitant because exports are right now reserved for props. On the module level, you can also export interfaces/types, but the meaning is different. I'm not sure if it would bring more confusion than helping. On the other hand one could argue that it does align with the "public API" semantics of export let
.
If we go with ComponentGeneric
I think I have one more argument in favor of implicit global: If it's an explicit import, people might think they can model their API like this outside of Svelte components, which is wrong. With a global type, they would see that TS will throw an error ("unknown type") if you try that outside.
Just throwing stuff at the wall...
<script lang="ts">
import {createEventDispatcher} from "svelte";
// this is wacky as all hell but kinda sorta less globals (syntax is more rigid??)
type T<V extends boolean = Generic> = V
type X<V = Generic> = string
export let array1: T[];
export let item1: T;
export let array2: X[];
const dispatch = createEventDispatcher<{arrayItemClick: X}>();
</script>
Honestly, this is the only alternative I could come up with. I think ComponentGeneric
is probably the way to go unless anyone gets any ideas or an epiphany. Maybe my attempt will inspire someone, lol.
EDIT: Hm, actually, this should work too:
<script lang="ts">
import {createEventDispatcher} from "svelte";
export type T<V extends boolean = Generic> = V
export type X = string
export let array1: T[];
export let item1: T;
export let array2: X[];
const dispatch = createEventDispatcher<{arrayItemClick: X}>();
</script>
It's a bit nicer if you have default values, but I still don't think it's good enough. The noise coming from declaring a type parameter is pretty high.
also I prefer the export type
solution in general as it has a lot less noise dont at me
Also, wouldn't using $$
prefixes instead of Component
match Svelte patterns better?
<script lang="ts">
import {createEventDispatcher} from "svelte";
type T = $$Generic<boolean>; // extends boolean
type X = $$Generic; // any
// you can use generics inside the other interfaces
interface $$Slots {
default: { aSlot: T }
}
export let array1: T[];
export let item1: T;
export let array2: X[];
const dispatch = createEventDispatcher<{arrayItemClick: X}>();
</script>
Adding invalid TS syntax is a no-go for me, it would require every IDE to know of this and people need to get used to that for Svelte only. Moreover, there wouldn't be things like optional generics. Generics need to be driven by the prop input.
Can you explain more why optional generics shouldn't be a thing? I'm not quite grasping your reasoning.
But if there aren't optional types, you can just use the right hand side as the base type.
export type T = string; // generic type T extends string
I'm hesitant because exports are right now reserved for props. On the module level, you can also export interfaces/types, but the meaning is different. I'm not sure if it would bring more confusion than helping. On the other hand one could argue that it does align with the "public API" semantics of export let.
They're modeled explicitly after the way props work. I think following that model would make following this more intuitive and more discoverable. For instance, setting a generic could be just like using a prop:
// Foo.svelte
<script lang="ts">
export type X = string;
</script>
// Bar.svelte
<script lang="ts">
import Foo from './foo';
interface Stuff {}
</script>
<Foo X={Stuff} />
I don't think setting them explicitly is planned as part of this rfc it's just an example to demonstrate my line of thinking.
Before I knew this wasn't possible yet, I tried doing stuff similar to this (to be met with errors of course).
@pitaj Sorry, but we can't make following happen:
<Foo X={Stuff} />
You can already create an uppercase prop (https://svelte.dev/repl/2897e47e9abf4858adc46978db9a1717?version=3.33.0), passing generics explicitly would require different syntax. This proposal doesn't cover that, nor should it. Let's agree on first part that is generics declaration + possibility to infer them from props/events/slots passed to the component.
export type T extends boolean;
Strong no from me. This is invalid TS, we can't invent new syntax, in addition to @dummdidumm points, what if TS decides to add extends X
part in future version with different meaning that ours.
If we go with ComponentGeneric I think I have one more argument in favor of implicit global: If it's an explicit import, people might think they can model their API like this outside of Svelte components, which is wrong. With a global type, they would see that TS will throw an error ("unknown type") if you try that outside.
That's a fair point. I still prefer export
but you're dragging me to your side step by step. I thought a bit more about implicit global implementation and it should be easier than I initially anticipated. This would be slightly parallel to $$props
so maybe @Monkatraz $$Generic
is a better fit.
Sorry, but we can't make following happen
I wasn't suggesting it, I was just demonstrating how my thought process went while initially looking for this feature.
Strong no from me. This is invalid TS, we can't invent new syntax
That's fair, alternatives have been proposed.
I agree with the $$
-syntax, it chimes well with the Svelte "language". Updates the rfc accordingly.
We did discuss this in the maintainer's meeting and I got positive feedback on the current state. One thing that came up is the possibility for the ability to export the interfaces for usage in other components to extend it there. Got to think about how that might work in combination with generics (maybe it won't).
Is it a goal of this RFC to propose a way to explicitly designate generic types, or would you rather require all generics be derived from the given props?
For explicitly designating types, would it not be possible to add a new syntax similar to a prop modifier generic:<GenericTypeName>(={<Type>})
that could be used like so:
<Component generic:X={string} />
Or like so with a shorthand
<script lang="ts">
interface Item {
name: string;
id: number;
}
</script>
<Component generic:Item /> <!-- `Component` has a generic named `Item` -->
Perhaps one can only explicitly designate the generic type if it is export
ed.
Explicitly declaring the generic is out of scope of this rfc, and at the moment I'm not even sure something like this is needed, since I don't see how generics can be driven by something else than props - and if you pass in these props, the generic type should be inferred from that.
I think one is where the prop types are derived from the generic:
types.d.ts
interface A<T> { ... }
interface B<T> { ... }
Component.svelte
<script type="ts">
type X = $$Generic;
export let a: A<X>;
export let b: B<X>;
</script>
Other.svelte
<script type="ts">
import Component from './Component.svelte'
type Union = string | number;
let a: A<Union> = 'Hello, world';
let b: B<Union> = 123;
</script>
<Component a b />
Inference probably could figure out that X == Union
, but does this hold in any case, no matter the complexity?
I know explicitly passing type parameters is out of the scope, but I'd just like to point that it's an already resolved thing in JSX, so if it's ever needed for Svelte in the future, JSX can be followed:
<Formik<FormValues>
initialValues={initialValues}
validateOnBlur={false}
validate={onValidate}
onSubmit={onSubmit}
>
The Svelte compiler would need to be adjusted then, which would be the first time that would need to be done for a typescript feature.
I know explicitly passing type parameters is out of the scope, but I'd just like to point that it's an already resolved thing in JSX, so if it's ever needed for Svelte in the future, JSX can be followed:
<Formik<FormValues> initialValues={initialValues} validateOnBlur={false} validate={onValidate} onSubmit={onSubmit} >
That doesn't do a whole lot though? Beside more precise compilation errors perhaps. Nowadays TS is pretty good at inferring so as long as each individual props inside the component are well typed relative to each other, it seems redundant to explicitely set the type on the call site? Anyway, it's not a must-have feature like the one discussed here!
@AlexGalays I'm not using Svelte in my projects yet, so I can't really say how useful explicit type parameters would be. What I do know is that in JSX they are very useful for example in the following snippet:
<Formik<FormValues>
initialValues={{
name: ''
}}
/>
Without an explicit type parameter, initialValues
would accept anything and try to infer the type from that. But what if I already have a type and want to make sure initialValues
conforms to it? Then I have to either (a) declare const initialValues: FormValues = { ... }
somewhere outside of the JSX or (b) pass FormValues
as an explicit type parameter and keep initialValues
inlined.
Anyway, I'm not saying that it should be implemented, I'm just throwing some information for someone reading this in the future in case it's ever decided explicit type parameters are needed.
Experimental support for typing props/events/slots and generics is now available as described in this RFC. Please provide feedback in the following issue: https://github.com/sveltejs/language-tools/issues/442 The feature is experimental and subject to change. It therefore doesn't follow semantic versioning yet.
https://github.com/sveltejs/language-tools/issues/1326 proposed to add style props (<Component --style-prop="value" />
) to this, too. It also brought up the good question about "what do JS users do?" because they can't use interfaces, they have to rely on JSDoc. We should probably enhance the RFC and find a JSDoc-compatible version, too. I think we can take inspiration for the API from the great work of sveld (kudos to @metonym).
Note to self: If we add support for generics via JSDoc, we need to adjust the transformation because right now it relies on using TS syntax in the transform output (since we know it's TypeScript). Not sure if we can use the same transformation then.
Thank you for the awesome work!
Is there example for generic parameter defaults? I can't find that in the RFC.
function foo<T extends Item = string>(..)
What's the use case for specifying defaults? Could you give an example where this is helpful/needed?
I made a function with generic parameter defaults like this:
export const defaultGen = () => Math.random().toString(16).substring(2);
function Likftc<
Source extends number | string,
Target extends number | string = string
>(
initialKeys: Source[] = [],
generator: () => Target = defaultGen as () => Target
)
The function type will be like:
let target = "foo";
Likftc([1, 2, 3], () => target).get(1) // get: (item: 1 | 2 | 3) => string
let target = 1234;
Likftc([1, 2, 3], () => target).get(1) // get: (item: 1 | 2 | 3) => number
Likftc([1, 2, 3]).get(1) // get: (item: 1 | 2 | 3) => string
// without generic parameter defaults
Likftc([1, 2, 3]).get(); // get: (item: 1 | 2 | 3) => string | number
With the generic parameter defaults I can use the default type if the argument is not passed.
I also make a react hook depending on it like this:
function useLikftc<
Source extends number | string,
Target extends number | string = string
>(frame: Source[], generator?: () => Target)
And it will work like:
let target = 123;
useLikftc([1, 2, 3], () => target).get(); // get: (item: 1 | 2 | 3) => number
let target = "foo";
useLikftc([1, 2, 3], () => target).get(); // get: (item: 1 | 2 | 3) => string
useLikftc([1, 2, 3]).get(); // get: (item: 1 | 2 | 3) => string
// without generic parameter defaults
useLikftc([1, 2, 3]).get(); // get: (item: 1 | 2 | 3) => string | number
I use component with let:
directive as the react custom hook alternative in svelte (reference: https://gradientdescent.de/custom-hooks/#Mypreferredsolution). The svelte component props are like this:
type Source = $$Generic<number | string>;
type Target = $$Generic<number | string>; // here is the generic parameter defaults needed
export let keys: Source[];
export let generator: (() => Target) | undefined = undefined;
The component would be used like:
<Likftc keys={[1, 2, 3]} generator={() => 'foo'} let:get /> <!-- get: (item: number) => string -->
<Likftc keys={[1, 2, 3]} generator={() => 'foo'} let:get /> <!-- get: (item: number) => number -->
<Likftc keys={[1, 2, 3]} let:get /> <!-- get: (item: number) => string | number -->
<!-- It's better to be typed as `get: (item: number) => string` in this case -->
With the generic parameter defaults, the component could be defined with the default type if the property is not passed. For now let:get
will return with type get: (item: number) => string | number
if I don't pass generator
and it's better to be typed as get: (item: number) => string
.
I just stumble across this the other day and I really like it. Thanks for the awesome work.
One thing I noticed though: If I try to override a event that is already predefined by svelte it wont give the correct type.
const dispatch = createEventDispatcher<{'input': Foo, 'customInput': Foo }>();
Produces the following types:
on:input // CustomEvent<any>
on:customInput // CustomEvent<Foo>
This is true for all predefined Events that I tested (submit
, change
, input
...)
The ability to override would be very nice IMO.
Is there a way to define defaults on generics? In TS, you can do T extends SomeInterface = SomeClass
, and it will know, if you didn't supply T, that T is the SomeClass
type. Can we do the same with this solution?
I also seem to be having trouble returning a generic from a function. No matter what you give the generic, it always types the return value in the widest way possible. Here's an example:
<!-- Example.svelte -->
<svelte:element this={tag} bind:this={element} {...$$restProps}
><slot /></svelte:element
>
<script lang="ts">
type TagName = $$Generic<'input' | 'span'>;
interface $$Props
extends svelte.JSX.HTMLAttributes<HTMLElementTagNameMap[TagName]> {
tag: TagName;
}
export let tag: TagName;
let element: HTMLElementTagNameMap[TagName];
export function getElement() {
return element;
}
</script>
<!-- UseExample.svelte -->
<Example tag="input" bind:this={example} />
<script lang="ts">
import { onMount } from 'svelte';
import Example from './Example.svelte';
let example: Example<'input'>;
onMount(() => {
const el = example.getElement();
});
</script>
When you hover over el
, you get const el: HTMLInputElement | HTMLSpanElement
as the type, and trying to use el.value
results in:
Property 'value' does not exist on type 'HTMLInputElement | HTMLSpanElement'.
Property 'value' does not exist on type 'HTMLSpanElement'.ts(2339)
Am I correctly following the RFC in this example? Is this an expected error?
I'm getting a bug when nesting multiple generic types within components
Main.svelte
<script>
import Foo from "./Foo.svelte"
</script>
<Foo xyz="foo" >
<div slot="option" let:xyz>
<h1>{xyz}</h1>
</div>
</Foo>
The type of xyz here is unknown
when it should be string
Foo.svelte
<script lang="ts">
import Bar from "./Bar.svelte"
type T = $$Generic
export let xyz: T
</script>
<Bar {xyz}>
<svelte:fragment slot="option" let:bar>
<slot name="option" xyz={bar}/>
</svelte:fragment>
</Bar>
Bar.svelte
<script lang="ts">
type T = $$Generic
export let xyz: T
</script>
<slot name="option" bar={xyz} />
I've tried the generic feature of components, really helpful! Suggestions:
bind:this
, or when you want to pass a component constructor to a different component). Yes, I could say explicitly what I want there, but why? Having a default value also suggests the user (that use a generic component library, say) what the 'default-behavior' or the 'unspecified' value should be.@typedef
.
rendered