microsoft / TypeScript

TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
https://www.typescriptlang.org
Apache License 2.0
101.14k stars 12.5k forks source link

Allow an interface in the JSX namespace to specify the element class type #14789

Open LennyLixalot opened 7 years ago

LennyLixalot commented 7 years ago

We're writing a non-react library that uses TS's JSX support with custom interfaces defined in the JSX namespace. Unless we are very much mistaken (The documentation seems a little out-of-date in this area? Most of our current understanding comes from reading through issues on here, we may have missed something.) it seems that while ElementClass can be used to define the type of the element instance type there is no interface to limit the members of the element class type.

We have a custom component base-class and an equivalent to React's createElement function. Our createElement function needs to be able to create instances of the component classes. To be able to do this in a type-safe manner, we need to be able to specify an interface for the element class type that can require a specific constructor signature.

This feels like an oversight in the current implementation TBH. With React itself it's even possible to create components with incompatible constructors that would fail at runtime.

RyanCavanaugh commented 7 years ago

Can you provide some example code so I can verify I'm understanding what you're saying?

RyanCavanaugh commented 7 years ago

Basically you want JSX.ElementClass, except for the constructor function type typeof X in the expression <X /> in addition to the current checking which checks the instance type X ?

LennyLixalot commented 7 years ago

Yes, exactly that.

Unless we've misunderstood something fundamental about how this whole JSX thing is working (we're .NET devs a little out of our comfort-zones, it's possible), the createElement function is called with typeof X as the first argument, and in order to really do anything useful with it we need to create an instance of X. If we could control the syntax of the constructor we would be able to do this in a reliable and type-safe manner.

For our particular use-case we'd probably want something like

type ComponentConstructor<TProps> = new (props: TProps) => Component<TProps>

or maybe

type ComponentConstructor<TComponent extends Component<TProps>, TProps> = new (props: TProps) => TComponent

Slightly simplified example of course.

LennyLixalot commented 7 years ago

Note the plan would be to have our base-class implement this constructor and assign the props as required, so in most cases the component classes wouldn't even need to do anything with constructors at all, but if they did they'd be forced to comply.

RyanCavanaugh commented 7 years ago

Seems reasonable to think about. We didn't implement anything like this earlier simply because React components almost never override their base class constructor, thus there's little chance for error.

LennyLixalot commented 7 years ago

If we could get this and #13618 (setting limits on children) then this feature would feel much more complete and applicable to a larger-range of DSL-use-cases.

dead-claudia commented 6 years ago

BTW, Mithril needs this for TS users to be able to use anything besides classes for components if they use JSX. And we have a lot of components that aren't - a lot of people prefer either object or closure components, and even our docs don't just use class components.

This is basically a blocker for us to support JSX in TS in any real capacity, if that helps explain a good use case for adding this feature.

For us, something as simple as type ElementType<P> = ... would work. (We can't use an interface, since we need a union here.) The return type is just the name of a valid component type for a factory.

dead-claudia commented 6 years ago

BTW, we do support JSX in JS, so TS is the odd one out here. Just thought I'd clarify.

dead-claudia commented 6 years ago

@RyanCavanaugh Do you have any status update on this? I know it's a bit of an old bug.