Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proposal: JSX.ElementType #13890

Open
antanas-arvasevicius opened this issue Feb 5, 2017 · 16 comments
Open

Proposal: JSX.ElementType #13890

antanas-arvasevicius opened this issue Feb 5, 2017 · 16 comments
Assignees
Labels
Domain: JSX/TSX Relates to the JSX parser and emitter In Discussion Not yet reached consensus Suggestion An idea for TypeScript

Comments

@antanas-arvasevicius
Copy link

Hello,
There are many GUI frameworks (ExtJs, SmartClient, OpenUI5) which do not works in React way and they could be easily be integrated with JSX/TSX if their JSX expressions would return correct "element type" e.g.
const listGrid = <ListGrid/> ; // should be type of ListGrid or isc.IListgrid, not JSX.Element

My proposal would be to add new special type into global JSX namespace - ElementTypeProperty.

JSX.ElementTypeProperty

Given an element instance type, we need to produce a type that will be return type of that JSX element. We call this the element type.

The interface JSX.ElementTypeProperty defines this process.
It may have 0 properties, in which case element instance type will be element type.
Or 1 property, in which case the element type will be the type of that property type of element instance type.

Note: Intrinsic lookup is not affected by ElementTypeProperty.


Related: #13746

antanas-arvasevicius pushed a commit to antanas-arvasevicius/TypeScript that referenced this issue Feb 5, 2017
antanas-arvasevicius pushed a commit to antanas-arvasevicius/TypeScript that referenced this issue Feb 5, 2017
@RyanCavanaugh RyanCavanaugh added In Discussion Not yet reached consensus Suggestion An idea for TypeScript labels Feb 6, 2017
@RyanCavanaugh
Copy link
Member

I think this is a reasonable idea. I recall some other suggestions to this effect and it seems like this behavior is going to be relatively common among non-Reactlike frameworks.

My main concern is that it seems really plausible that some framework would define ListGrid to be a reference to an object containing a create method or other factorylike pattern where the name of the element just gives you something that needs to go through overload resolution. We might mitigate that with one of the open proposals to support typeof producing the return type of a function call, though.

@antanas-arvasevicius
Copy link
Author

Hi,
you can look at our implementation of Isomorphic SmartClient's JSX support.
Shortly:
All SmartClient components has a create method in it, e.g. isc.ListGrid.create, but the problem is that "children" property will differ, in ListGrid it would be fields, in Canvas would be children, in VLayout would be members. So we've took approach by defining new classes which has different factorylike public static create(...) methods which produces GUI object. Then added SmartClientJSX .createElement(...) which just delegates create task to these static create factories.
I think other frameworks would also need that manual wiring too, but if they are consistent maybe something like typescript interface ElementTypeProperty { create(); } would work?
What's your thoughts on this?

smartclient.jsx.d.ts:

declare module JSX {
    export interface IntrinsicElements { }
    interface Element extends isc.ICanvas { }
    interface ElementClass extends Component<any, any> { }
    interface HtmlElementInstance extends ElementClass { }
    interface ElementAttributesProperty { __props; }
    interface ElementTypeProperty { __elementType; }
}

smartclient.gui.ts: (Note: all isc.* definitions are written in separate .d.ts file)

namespace SmartClientJSX {
    export function createElement<P extends Component<T, M>, T, M>(elementClass: {new (...args: any[]): P}, props: T, ...children: Component<any, any>[]):M {
        return (<any>elementClass).create(props !== null ? props : {}, children);
    }
}

abstract class Component<T, M> { private __props: T; private __elementType: M;
    static create(params: any[], children) {}
}

// property 'children' will be children objects.
class Canvas extends Component<isc.ICanvasOptions, isc.ICanvas> {
    static create(params, children) {
        return isc.Canvas.create({
            ...<any>params,
            children: params.children ? [...params.children, ...children] : children
        });
    }
}

// property 'members' will be children
class VLayout extends Component<isc.IVLayoutOptions, isc.IVLayout> {
    static create(params, children) {
        return isc.VLayout.create({...<any>params, members: children});
    }
}

// property 'fields' will be children
class ListGrid extends Component<isc.IListGridOptions, isc.IListGrid> {
    static create(params, children) {
        return isc.ListGrid.create({
            ...<any>params,
            fields: params.fields ? [...params.fields, ...children] : children
        });
    }
}

// no children allowed, you can see that ListGridField for ListGrid is just plain object.
class ListGridField extends Component<isc.IListGridFieldOptions, isc.IListGridField> {
    static create(params) {
        return {...params};
    }
}

// property 'fields' will be children 
// allow to pass fields in attribute "fields" and concat children as fields.
class DynamicForm extends Component<isc.IDynamicFormOptions, isc.IDynamicForm> {
    static create(params, children) {
        return isc.DynamicForm.create({
            ...<any>params,
            fields: params.fields ? [...params.fields, ...children] : children
        });
    }
}

// FormFieldItem for DynamicForm.fields is just plain object
class FormFieldItem extends Component<isc.IDynamicFormFieldOptions, isc.IDynamicFormField> {
    static create(params) {
        return {...params};
    }
}

// property 'tabs' is children
class TabSet extends Component<isc.ITabSetOptions, isc.ITabSet> {
    static create(params, children) {
        return isc.TabSet.create({...<any>params, tabs: params.tabs ? [...params.tabs, ...children] : children});
    }
}

class Tab extends Component<isc.ITabOptions, isc.ITab> {
    static create(params) {
        return {...params};
    }
}

antanas-arvasevicius pushed a commit to antanas-arvasevicius/TypeScript that referenced this issue Feb 7, 2017
antanas-arvasevicius pushed a commit to antanas-arvasevicius/TypeScript that referenced this issue Feb 7, 2017
antanas-arvasevicius pushed a commit to antanas-arvasevicius/TypeScript that referenced this issue Feb 7, 2017
@antanas-arvasevicius
Copy link
Author

Hi, we are thinking to use forked version of tc internally till this feature will be released to public if this feature will be accepted in future. I know that you have more priority work, but is it possible to know how long should it take till this request will be discussed and be accepted or rejected? Thanks.

@yuit yuit added the Domain: JSX/TSX Relates to the JSX parser and emitter label Feb 16, 2017
@RyanCavanaugh
Copy link
Member

@antanas-arvasevicius we talked about this for a while yesterday and had a bunch of questions. Is there a repo that uses this that I could look at to better understand the behavior? Otherwise I can post the Qs here

@antanas-arvasevicius
Copy link
Author

antanas-arvasevicius commented Mar 15, 2017 via email

@antanas-arvasevicius
Copy link
Author

Hi, Ryan,
I've just made some demo where you can explore the workings of JSX ElementType:
https://github.com/antanas-arvasevicius/elementtype-demo

just git clone && npm install && tsc && node server.js

demo will be on http://localhost:9999/

You can see that now we must explicitly cast each :
/src/page/demo/DemoPageLayout.tsx
/src/layout/MainLayout.tsx
/src/page/demo/Dialog.tsx

Using patched compiler in PR #13891 explicit casting could be removed.

Any questions are welcome.

@antanas-arvasevicius
Copy link
Author

Hello, @RyanCavanaugh , could you please give any feedback for this issue? Are there are any plans to support this and how is it going?

@RyanCavanaugh
Copy link
Member

I have a lot of open questions on this with regards to how general-purpose it is. Specifically, the problem of whether the element type appears on the instance side or static side of the constructor function - there seem to be requests from people on both sides of this. The major contingents are:

  • Frameworks that work like WPF/XAML and create instances+set properties on the actual type specified (the element type is the class instance type)
  • Frameworks that work like React and do opaque deferred instantiation (the element type is a fixed JSX.ElementType)
  • People who want to encode additional semantics into their React-like frameworks, e.g. they want a <Foo><bar/></Foo> construct to somehow enforce that only <bar /> or <baz /> elements are passed as children to Foo even though the deferred instatiation might otherwise imply an opaque element type
  • Frameworks that do immediate or deferred instatiation of some other type (yours as far as I can tell) ?
    • Open question is whether elements here should be assignable based on the constructor function type, or the instance type

My current thinking is that a long-term solution would be changing the JSX element type resolver to be similar to resolving the return type of a function. This opens up basically arbitrary possibilities (via generic type inference) but is quite a bit more complex.

@antanas-arvasevicius
Copy link
Author

Thank you for your detailed response. Yes, seems complicated problem :)
For specifically our case, we are "integrating" open source GUI framework into TSX coding style and we cannot have "class instance type" because original GUI objects doesn't work well and we need to have some "wrappers" which instantiates these original GUI objects. So yes we need an ability to somehow define that if you would write: const layout = <HLayout/> you would get an instance of "some other type" which is as in my proposed variant would be the type of " __elementType" property of the HLayout class defined by interface ElementTypeProperty { __elementType; } similiar workings as with attributes (interface ElementAttributesProperty { __props; })

And if there is a need to have an actual type specified it could be defined as ``interface ElementTypeProperty { }` (empty interface)

Is this solution doesn't cover full cases? (1, 2, 4 I think is covered, for 3 - is this question in this jsx element scope?)

About some "type resolver function" and using "return type" as a result would solve all problems, but it will be difficult to "parse/execute" this during type checking? If it wouldn't the ElementAttributesProperty could be implemented in the same way.

@morrisallison
Copy link

My current thinking is that a long-term solution would be changing the JSX element type resolver to be similar to resolving the return type of a function. This opens up basically arbitrary possibilities (via generic type inference) but is quite a bit more complex.

Great, this would solve the issue I mentioned here. #4195 (comment)

@ceymard
Copy link

ceymard commented May 12, 2017

I have a use case for a library I'm writing where I'm basically using tsx to generate DOM nodes directly (with extra sauce on top).

I would like a lot to be able to specify that <div/> returns a HTMLDivElement or an HTMLInputElement instead of just a plain Node as I am doing right now with some ugly casts.

Couldn't the whole tsx transform simply apply the function call and type check it ? My guess is that it would also solve the whole generics issue since the type inferer works well with them -- directly calling the factory function without using tsx generally works fine...

@antanas-arvasevicius
Copy link
Author

Hi,
looks like this feature is still not on your (public) roadmap list, is there are any technical issues to implement it? ("JSX element type resolver to be similar to resolving the return type of a function.")
If it's technically doable then I can try to contribute and implement it, just some guidelines on how to approach it would be nice to have.

@TotooriaHyperion
Copy link

Related: #23457

@morrisallison
Copy link

Related: #21699

@dead-claudia
Copy link

Related: #13260

@bsunderhus
Copy link

bsunderhus commented Jun 11, 2024

Any updates on this? or #14729

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Domain: JSX/TSX Relates to the JSX parser and emitter In Discussion Not yet reached consensus Suggestion An idea for TypeScript
Projects
None yet
Development

No branches or pull requests

8 participants