-
Notifications
You must be signed in to change notification settings - Fork 12.5k
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
Type JSX elements based on createElement function #14729
Comments
I think this is doable and could potentially cut out a lot of code from the compiler. When we added JSX support we didn't have spread types or The remaining difficulty is just technical - the overload resolution code is hardcoded in a lot of places with the assumption that it's operating over an actual argument list syntax tree rather than some abstract set of parameters. |
Having this would allow one to utilize the concept of generalized JSX with TS. Currently it seems impossible, because the return type of any JSX expression is always a non-generic In fact, given this it would make sense to ditch the whole "JSX" global namespace and just use the declared type of the locally scoped |
That's exactly what I thought!
The compiler already handles different kinds of function calls. @niieani That's an interesting use case. I think that it would be useful in a lot of situations, other than the DOM, where some domain specific language is desired. |
@ivogabe yeah, I especially like the example with ASTs, represented by JSX. So much more readable than declaring plain objects for AST. |
I'd like to ping this issue ; could it be possible that the whole jsx mechanic be simply a transform to a function call with its implied checking ? This would allow for so much flexibility in library creation. |
No. Two reasons and there may be more:
[1] interface Point {
x: number;
y: number;
}
var m: Point;
var j: Point = { z: 10, ...m }; // Not an error due to spread |
@RyanCavanaugh The first could be fixed via other types of constructs that could be added to the language:
Second we could think about. Maybe the spreads in JSX would behave differently? There's probably something we could do. In any case, this would massively simplify the API and allow for custom JSX applications as I've mentioned in the comment above. |
AFAIK it's not just class Foo extends React.Component<{a:string}>{}
//This works:
<Foo b-3="bar" a="ggg"/> |
@RyanCavanaugh now that the second point has been fixed, can this be revisited? interface Point {
x: number;
y: number;
}
var m: Point;
var j: Point = { z: 10, ...m }; // NOW ERRORS |
Another problem to consider here is how to deal with backwards compatibility, particularly around the DefinitelyTyped definitions for React, which would have to be updated to support this. Existing types that depend on the JSX namespace would no longer work because the compiler wouldn't look there anymore. The most graceful way I can see to handle that would be to modify the React definitions so that the JSX namespace definitions are dependent on (or at least structurally equivalent to) whatever types are used for For non-React use cases, this will probably just have to be a breaking change, with a suggested migration pattern. If someone is already using the JSX namespace for typing JSX, all they need to do is add a I agree that having this would improve the flexibility of JSX-in-TS considerably, but the migration for existing code will probably be quite painful. |
should be covered by #21699 |
I've been following various JSX threads over the years all boiling down to this, I really truly hope this lands in TS at some point even if not 5.1 directly.
In response to this question, I sincerely hope for it to be 1. The type-safety of these functions seems most important over performance issues. The Effect community (formerly fp-ts) and I are building libraries today for html rendering and are today forced to use tagged template literals to be able to track typed resources and typed errors of children to be able to aggregate the resources required to run them and the errors they might produce as unions. If JSX uses createElement as any other function for type-checking, we'd be able to utilize JSX's AST to further optimize them using compiler techniques and provide much more type-safe APIs for properties which is a lot more cumbersome to do with template literal strings and requires language service/server plugins to work as expected. |
@RyanCavanaugh You closed this with #51328, but it doesn't at all lookup the types by the signature of createElement. I think this should remain open |
I just add here a pure sample code of the problem: https://codesandbox.io/s/jsx-element-loses-types-6nmh3m?file=/src/App.tsx |
Maybe there's something I don't get here. Why is it not possible to allow
I'm new here, so maybe I'm missing something. I do know that this approach might have some performance issues similar to the one discussed here. Also, does Typescript always pass the |
It could be an opt-in feature behind a TSConfig option since it'll break existing code. |
So, @MartinJohns and @xxaff, do you think it's a bad idea? I'll like to learn. I'm new to the TS world. |
This was planned for milestone 5.1. Any updates here? |
Is there still any interest or plans for this? This seems like an incredible feature that’ll make libraries utilizing JSX far more versatile. 😄 |
Here's a couple TS Playground examples of how we could use this feature to enable similar features to flow's "renders" types and limiting allowed children or other props that receive jsx: |
TypeScript currently uses a JSX namespace to type the props of JSX elements. This is customisable by modifying
JSX.IntrinsicElements
,JSX.ElementClass
. However, the type of an element is alwaysJSX.Element
.Of course more interfaces could be added to the
JSX
namespace. However, I think that with less effort, the compiler could be made more flexible. I'd propose to check JSX elements based on a (virtual) call toJSX.createElement
. For instance, the current behaviour can approximately be written like this:Given that function signatures are well customisable with the use of generics for instance, most requests can be implemented this way. For instance, #13890 and #13618 would benefit from this change. Libraries that use JSX, but not on the React-way, will benefit from this too.
Proposed semantics
For a JSX element, a virtual call to
JSX.createElement
is constructed. It is passed three arguments:<div />
). Otherwise, the identifier is passed (Foo
for<Foo />
).This should be roughly the same as the JSX transform used in the emitter phase. One notable difference is the following: in case of no properties or no children, an empty object or an empty array should be passed, instead ignoring the argument. This makes it easier to write
JSX.createElement
for library authors.Backwards compatibility
For backwards compatibility, one of the following approaches could be taken:
JSX.createElement
does not exist.JSX.createElement
does not exist, default it to (roughly) the definition above.Error reporting
When type checking the generated function call, the checker can give error messages like "Argument of type .. " or "Supplied parameters do not match any signature of call target". These messages should be replaced when checking JSX elements.
The text was updated successfully, but these errors were encountered: