stencil
stencil copied to clipboard
Generic components fail to typecheck
Stencil version:
@stencil/[email protected]
I'm submitting a:
[x] bug report [ ] feature request [ ] support request => Please do not submit support requests here, use one of these channels: https://stencil-worldwide.herokuapp.com/ or https://forum.ionicframework.com/
Current behavior:
When you try to compile a component that has a generic type based on a property type it fails to compile:
[ ERROR ] TypeScript: ./src/components.d.ts:152:17
Cannot find name 'Data'.
L151: */
L152: "data": Data[];
L153: /**
Expected behavior:
The component should compile without issues
Steps to reproduce:
Related code:
If I have a component, say a table-like component that operates on generic types (e.g. emits events of said types), is it possible to make it generic?
@Component(/* ... */)
export class MyTable<Data> {
@Prop() data: Data[] = [];
get result(): Data[] {
// data will be sorted, filtered and sliced
}
// Pass the result to parent.
@Event() update: EventEmitter<Data[]>
}
Now when a consumer sets the data property of <my-table>
to say Pokemon
you would expect e.g. the update event to reflect that.
type Pokemon = {
name: string;
type: string;
cp: number;
};
const pokemon: Pokemon = [
{ name: 'Pikachu', type: 'Electric', cp: 500 },
{ name: Magikarp, type: 'Water', cp: 23 },
];
myTable.data = pokemon;
myTable.addEventListener('update', (event) => {
// `event.detail` and `event.target.result` should be of type `Pokemon[]`
});
Other information:
This is only a minor annoyance as it is trivial to use unknown
in this case. But it could offer a nice quality of life if fixed.
I think this may be more a limitation of TypeScript than anything else, but I agree it'd be pretty slick if TS became smart enough to infer a more specific type for myTable
based on the data
property assignment there.
Some alternatives that may work:
- ensure that
myTable
is typed asMyTable<Pokemon>
via type declaration, or cast, etc. - use a factory function to instantiate
MyTable<T>
. Something likecreateTable(props: MyTableProps<T>): MyTable<T>
- you might get accurate type inference if you use instantiate the component via JSX. Not sure about that one though.
edit: "Contextual Narrowing for Generics" is on the TS roadmap, but as far as I can tell it's just for control flow analysis, which wouldn't help here.
I see so before contextual narrowing I would need to typecast after a fetch the element from the dom:
const myTable = document.querySelector('my-table') as MyTable<Pokemon>;
However my main issue is that the generated components.d.ts
doesn’t add the generic annotation to the interface:
interface FlTable {
"data": Data[];
}
But what we want is:
- interface FlTable {
+ interface FlTable<Data> {
"data": Data[];
}
I wouldn’t mind if other instances are set to unknown
by default:
var HTMLFlTableElement: {
prototype: HTMLFlTableElement<unknown>;
new <Data>(): HTMLFlTableElement<Data>;
};
etc.
What I’m mainly looking for is an internal consistency in the implementation (e.g. I want to refer to the generic type in a provided sort
property, filter
property etc.)
Again this is not something that is desperately needed needed. Annotating with unknown
works fine, it just feels a little dirty.
However my main issue is that the generated components.d.ts doesn’t add the generic annotation to the interface
Oh, my bad -- I didn't notice that part of the problem.
As you've indicated, generic types for class components don't seem to be supported, which is pretty unfortunate 😞 I can't think of any good reason why they couldn't be supported, but perhaps someone who knows better can chime in.
I guess there's always Functional Components 🤷♂️
I ran into this as well today - specifically when trying to output a react component. I'd like to be able to pass the generic type to infer it's value type. here's my example:
export class RadioNested<T>
Then when consuming in my react application I CANNOT do this:
<TnRadioNested<string>
/>
Because the generic is missing from the components.d.ts:
interface TnRadioNested {
"headerTitle"?: string;
"onDidDismiss"?: (event: CustomEvent<T>) => void;
"onRadioChange"?: (event: CustomEvent<{ selectedRadioValue: string }>) => void;
"open"?: boolean;
"options": RadioNestedOption<T>[];
"popoverFromElementId"?: string;
"selectedRadioValue"?: string;
}
The react output target would also need to allow for the generic for my implementation to work
Sorry this is somewhat unrelated, but I ended up back here while researching a separate issue that overlaps a bit with the initial example:
myTable.addEventListener('update', (event) => {
// `event.detail` and `event.target.result` should be of type `Pokemon[]`
});
Even without the use of generics, I don't see accurate type inference here for the event type. It's always any
. It looks to me like the compiler outputs event typings for JSX, but not for the DOM Element APIs that come into play when using addEventListener
and removeEventListener
.
Is this everyone's experience, or am I missing something?
https://github.com/ionic-team/stencil/blob/16b8ea4dabb22024872a38bc58ba1dcf1c7cc25b/src/compiler/transformers/static-to-meta/parse-static.ts#L32
https://github.com/ionic-team/stencil/blob/16b8ea4dabb22024872a38bc58ba1dcf1c7cc25b/src/compiler/build/build-ctx.ts#L8
https://github.com/ionic-team/stencil/blob/fa38ada12cc102d0539c24092db2e0ef437cdf6c/src/compiler/transpile/run-program.ts#L14
https://github.com/ionic-team/stencil/blob/267e3dd03887eafa7b58f4d4efae9bc833cd6581/src/compiler/types/generate-app-types.ts#L67
https://stackoverflow.com/questions/57521951/how-to-get-generic-type-from-a-method-signature-return-type
We must find the class signature and add it to the componentClassName.