Stencil version:
@stencil/[email protected]
I'm submitting a:
Current behavior:
The build breaks if you using certain property types in a component.
For example:
@Component({
tag: 'formatted-list'
})
export class FormattedList<T = any> {
// The list of values to be displayed
@Prop() values: T[];
// The function to transform a value to HTML
@Prop() formatter?: ((value: T) => JSX.Element);
// Delimiter between values
@Prop() delim?: JSX.Element;
render(): JSX.Element {
const {values, formatter} = this;
if (!values || !values.length) return null;
const delim = this.delim || ', ';
const result: JSX.Element[] = [];
for (const value of values) {
if (value != null) {
if (result.length) result.push(delim);
result.push(formatter ? formatter(value) : value.toString());
}
}
return result;
}
}
If you run nom start you get the following error:
[05:16.2] @stencil/core v0.0.8-10
[05:16.2] build, app, dev mode, started ...
[05:16.2] compile started ...
[05:19.0] compile finished in 2.85 s
[ ERROR ] typescript: src/components.d.ts, line: 37
Cannot find name 'T'.
[ ERROR ] typescript: src/components.d.ts, line: 38
Namespace 'global.JSX' has no exported member 'Element'.
These errors are related to the components.d.ts file where the attributes are defined:
namespace JSXElements {
export interface FormattedListAttributes extends HTMLAttributes {
values?: T[];
formatter?: ((value: T) => JSX.Element);
delim?: JSX.Element;
}
}
Expected behavior:
It should be possible to use any valid TypeScript data types when creating components. This example is an attempt to create a simple reproducible case that illustrates the problems. I realize that it could be implemented differently to avoid the problems but the actual components I am working on are more involved.
The specific things I am trying to do in my components are:
T[] to any[] would not be able to enforce the parameter type for the formatter function.JSX.Element as a property value. This allows flexible values including strings or html. The error may also mean that other kinds of global types would also have problems.Steps to reproduce:
The errors occur with version 0.0.8-10. When I revert to 0.0.8-8 the same errors exist in the components.d.ts file but at least I am able to start the application.
Potential approach:
One potential way to solve this problem would be to have the data type of the attributes reference the data type of the component class property by name. This is similar to how things like Partial<MyClass> work. For example:
namespace JSXElements {
export interface FormattedListAttributes extends HTMLAttributes {
values?: FormattedList['values'],
formatter?: FormattedList['formatter'],
delim?: FormattedList['delim']
}
}
This would result in values: any[], etc.
It would not enforce type safety based on generics across attribute values, but the errors would go away.
The approach could be further simplified using Pick<>.
For the above example the components.d.ts file could contain:
export interface FormattedListAttributes extends
HTMLAttributes,
Pick<FormattedList, 'values' | 'formatter' | 'delim'> { }
This would restrict it to the named properties (vs. all members of the component class) and they would have the same data type as the component properties.
This could also be used for more accurate element types.
For example:
interface HTMLFormattedListElement extends
Pick<FormattedList, 'values' | 'formatter' | 'delim'>,
HTMLElement { }
Generics could also be supported:
interface HTMLFormattedListElement<T = object> extends
Pick<FormattedList<T>, 'values' | 'formatter' | 'delim'>,
HTMLElement { }
In this case the list of properties would include all members that are implemented on the element (including methods) but not members that only exist on the component class.
The Type system generation has changed a lot. I am closing this issue because I believe this was fixed.
Using JSX.Element as property type works now.
Using generic class for component does not work. For example, export class FormattedList<T = any> { ... }.
@cjorasch I have been thinking about supporting this but how would you use generics in JSX Elements?
Currently the main value would be explicit typing of related properties, fields, parameters (see FormattedList example above). This allows for explicit assumptions when writing a component vs. having lots of any data types.
Type checking would be available when Typescript adds support for generic components in Typescript 2.9.
https://github.com/Microsoft/TypeScript/wiki/Roadmap
https://github.com/Microsoft/TypeScript/pull/22415
Did not realize that was coming, Very cool! Since we are so closely typed to Typescript, it is likely that we will not allow for generics on Component Classes until 2.9 is released.
@jthoms1 using generics for props is still not working in 0.16.1:
export class ExampleComponent<T extends string | number> {
@Prop() value: T;
}
throws error:
[ ERROR ] TypeScript: src/components.d.ts:2:13
Cannot find name 'T'.
L2:
L3: 'value': T;
L4:
It's not just the @Prop decorator, the @Event decorator fails as well.
@Event() change: EventEmitter<T>;
Only the generic declaration of the component has to be passed to the .d.ts.
@jthoms1 This still doesnt work in Stencil & Typescript 2.9 with Generics for Component Classes has been released for over an year.
Why is this issue closed? This is not fixed!
@jthoms1 Any updates on this issue?
So it is 2021 now...any guidance on getting around this?