-
Notifications
You must be signed in to change notification settings - Fork 12.6k
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
JSX in TypeScript #3203
Comments
@fdecampredon has been helping with this |
This does not seem to address how/if |
This is not one of the open questions. But have you been looking at #3022? Would be good to know your thoughts. It would also be good to know the points on why you are supporting JSX in the first place? As I recall it TS where negative to having a first-class JSX support before and then you change your mind right? Having not stated the main points of why TS decided to support JSX would lead to everyone guessing you are supporting it for greed of more users? |
I really hope we can get a transformer for |
@RyanCavanaugh, why not offer the |
Over in Flow land, we've found great success with an expression-annotation/downcasting syntax: |
As an aside, let's please not have any more meta-discussion of the priority or motivations behind JSX support at this time (feel free to log a discussion issue, we'd be happy to talk about it!), the syntax of the Comment round-up follows
&&
We really don't want to re-implement JSX expansion in the compiler. It's pointless and dangerous to have two implementations of the same thing that must agree on all semantics. People can already use JSXTransformer.js to do this transform at runtime (for simple solutions that don't need top performance), or for projects that do need to compile their JSX ahead-of-time, they should already have a build pipeline for the sake of minification / bundling. It should be fairly simple for someone to write a
JSX was previously not one of the highest-priority things for us to do. Now it is (assuming we can get this design fleshed out). Regarding #3022, we covered this in the backlog slog today and I'll be posting comments there when I have time.
We will. This is explained in the write-up ("is available in both .ts and .tsx")
|
This makes sense, but just so it's explicit on what the behavior will be, I'm assuming you're saying that TypeScript output will contain the JSX. Taking your component example, if TypeScript is given this: interface MyFormComponent {
props: {
myName: string;
myValue?: number;
};
}
var MyForm: MyFormComponent = React.createClass(...);
var x = <MyForm myName={42} />; then it will output this: var MyForm = React.createClass(...);
var x = <MyForm myName={42} />; Which could then be fed to the JSX transformer (since it's plain JavaScript at that point). And going a step further, this: <div onClick={() => this.doSomething()} /> will output something like this: var _this = this;
<div onClick={(function() { return _this.doSomething() })} /> The last example just to say that TypeScript-specific syntax contained within JSX will be appropriately transformed into plain JavaScript? |
How about supporting this too then? var y = <MyClass { a: 3, b: 'foo'} />; I find it more ergonomic then typing multiple |
@RyanCavanaugh You said you don't want to discuss motivations or prioritization of React support in this thread- but is there somewhere for the community to discuss that? AFAIK, you guys don't post the design meeting notes anymore, so all these sorts of decisions about prioritization are being made behind closed doors with little community discussion. |
Does not using |
Also for spread attributes, how will you handle cases like :
|
Children type with React is something like:
However I don't think |
Also could you consider allowing the interface MyFormComponent {
props: {
myName: string;
myValue?: number;
};
}
function MyForm (): MyFormComponent {....}
var x = <MyForm myName={42} />; And if we don't find a constructor signature on |
Placeholder comment for things I have moved to other threads:
|
If JSX wants to add this syntax, we'd happily support it too, but we're not going to fork their DSL.
Feel free to log an issue asking that question and we can talk about specifics there. Prioritization has a ton of inputs -- technical reasons, business goals, community feedback, enabling partner teams, and so on; it's kind of intractable for us to post general stuff on how these decisions are being made. There is not any meeting with the concrete agenda "Decide which TypeScript features to do next".
Great point; I had not considered that case. Any ideas on what we might do instead?
Last in wins. I had originally thought this should be an error, but you could reasonably have something like this: interface DefaultThings {
foo?: number;
bar?: string;
}
var d: DefaultThings = { };
if(f) d.foo = f;
if(b) d.bar = b;
// Intentional overriding of previously-specified attribute
var x = <MyThing foo={defaultFoo} {...d} />
// Intentional overriding of previously-specified spread attribute
var y = <MyThing {...d} foo={definitelyOverrideFoo} />
Seems reasonable -- we could consider the construct signatures first, then fall back to the call signatures. |
For |
Working on some stuff in my fork with slightly different semantics based on this feedback. |
What is the type of //for intrinsic
var y = <div ref="() => void 0" key="id" id="something"><span /> text</div>;
//type is
{
type : string;
ref: () => void;
key: string;
props: { id: string; children: any[] }
};
//for element
interface MyFormComponent {
props: {
myName: string;
myValue?: number;
};
}
var MyForm: MyFormComponent = React.createClass(...);
var y = (
<MyComponent ref="() => void 0" key="id" myName="something">
<span /> text
</MyComponent>
);
//type is
{
type : MyFormComponent;
ref: () => void;
key: string;
props: { myName: string; children: any[] }
}; |
(moved this section to OP) Examples of Varied JSX BehaviorAll the No JSX Augmentationsdeclare module JSX { }
// Error, cannot find name 'myElem'
var a1 = <myElem />; Anything-goes JSXBy adding a string indexer to declare module JSX {
export interface Intrinsics {
[anything: string]: any;
}
}
// OK
var a1 = <myElem xzx="neat" />;
// Still an error here -- cannot multiply two strings
var a2 = <myElem foo={ 'x' * 'y'} />; Arbitrary Elements, Constrained AttributesMaybe we have a DSL with arbitrary element names, but constrained attributes: declare module JSX {
export interface ElementAttributesProperty {
settings;
}
export interface RaytracerObject {
x?: number;
y?: number;
z?: number;
material?: string;
}
export interface Intrinsics {
[anything: string]: {
new (): { settings: RaytracerObject };
}
}
}
// Error, type 'q' does not exist on 'RaytracerObject'
var a1 = <Sphere q="42" />;
// Error, type 'string' is not assignable to 'number'
var a2 = <Plane x="foo" />;
// OK
var a3 = <Cone x={Math.sin(Math.PI / 2) * 3} /> Constrained Elements, Arbitrary Attributesdeclare module JSX {
export interface Intrinsics {
Dog: any;
Cat: any;
}
}
// OK
var a1 = <Dog woof="woof" />
// Error, can't find name 'Pig'
var a2 = <Pig name="Babe" /> Class-based Object Modelsdeclare module JSX {
interface ElementClass {
toXML(): string;
}
interface ElementAttributesProperty {
attributes: any;
}
}
class Room {
toXML() {
return '<Room name=' + this.attributes.name + '/>';
}
attributes: {
name?: string;
}
}
class OtherThing {
// Forgot to add toXML()!
attributes: {
size: number;
}
}
// OK
var x = <Room name="kitchen" />;
console.log(x.toXML());
// Error, 'toXML' is missing on 'OtherThing'
var y = <OtherThing size="100" /> A Miniaturized React/// <reference path="src/lib/jsx.d.ts" />
declare module JSX {
interface SpecialAttributePrefixes {
'data-': string|number|boolean;
'aria-': string|number|boolean;
}
interface ElementClass {
render(): any;
}
interface ElementAttributesProperty {
props: any;
}
interface Intrinsics {
div: React.HtmlElementConstructor;
span: React.HtmlElementConstructor;
}
}
declare module React {
interface HtmlElementConstructor {
new (): HtmlElementInstance;
}
interface HtmlElementInstance extends JSX.ElementClass {
props: ReactHtmlElementAttributes;
}
interface ReactHtmlElementAttributes {
accessKey?: string;
checked?: string;
classID?: string;
className?: string;
id?: string;
name?: string;
ref?: string;
key?: string;
}
}
// Using intrinsics
// OK
var x = <div className="myDiv" data-myId="custom" />
// Error, cannot find name 'dvi'
var y = <dvi />
// Error, no property 'classname'
var z = <span classname="oops" />
// Classes
class MyComp implements JSX.ElementClass {
render() {
return undefined; // NYI
}
props: {
title?: string;
}
}
// OK
var m1 = <MyComp />;
// Error
var m2 = <MyComp tite="missed an L" />;
// Error, cannot find name 'Mycomp'
var m3 = <Mycomp title="hello" />; |
There is React Native. |
a few questions:
|
React-native has the same type semantics as React, no? We would just need a .d.ts file with a different set of names ( |
Yes, looks like it uses the same JSX transform. But |
Yep |
Got another blog post here http://blog.mgechev.com/2015/07/05/using-jsx-react-with-typescript/ |
@RyanCavanaugh is there a way of trying out JSX in VSCode? |
Yep, it's very easy: Replace Edit
to
|
Ok I will try it out 👍 |
It worked perfectly! |
For anyone else a bit confused as I was, regarding @RyanCavanaugh 's comment: the folder name appears to be Also, in order for it to not highlight your JSX as an error ("--jsx flag required"), you need to add a {
"compilerOptions": {
"target": "ES6",
"module": "commonjs",
"sourceMap": true,
"jsx": "preserve"
}
} The other option for "jsx" being "react", as described in @jbrantly 's excellent blog post. Then restart Code. It doesn't appear to pick up changes to either the config files, nor tsconfig. You can use the tsserver.js file from ntypescript if you want. Seems to work fine. I used Agent Ransack to find it because it's buried somewhere deep on my system. I suppose you could also just nab a copy off GitHub. |
this is some excellent work. by handling the complexity of reconciling TS and JSX, you've saved downstream developers like myself a great deal of effort. |
Second that, I converted a project over to use TSX and it works like a charm. |
@joewood what editor are you seeing no completions with |
@danquirk this is VS Code. To be clear, the completion isn't working inside an object literal. I wouldn't expect it to. So the following would show let x = <{foo:string, bar?:string}>{ foo:"hello" } But in a .tsx file, the type isn't asserted until after the literal, so the IDE doesn't know the type: let x = { foo:"hello" } as {foo:string, bar?:string}; It's not a huge deal. It was just useful for large interfaces, like React's CSSProperties etc... |
Any chance to add an option where it emits a string? |
Can you clarify with some examples? Note that JSX can have embedded code in it, so it's not clear that having a string representation of that would be useful |
I was thinking in the use of jsx highlighting and autocomplete to have simple html in js without any react features. |
The new TSX mode in the TypeScript compiler has the requirements that the element class constructor produces an element instance type that is assignable to the type JSX.ElementClass. To meet this Component<P, S> must declare `render(): JSX.Element`. Derived classes can be more specific. This should resolve microsoft/TypeScript#3203.
could anyone write step-by-step instructions how to get this working #3203 (comment) from the ground? |
@Bartq a new version of VSCode is due soon that includes a configuration setting for the TypeScript compiler. This will make the process much easier. |
@joewood - thanks, cool! When, tomorrow? ;) |
Updated 7/24 with some spec changes
TODO: Add language about string indexers on props types turning off surplus attribute errors
Up-front Requirements
To use JSX expressions in a TypeScript file, you must use the
tsx
extension. Additionally, during compilation, you must specify a--jsx <value>
. Supported values arepreserve
, which emits the JSX as-is (modulo TypeScript desugaring inside JSX Expressions), orreact
, which emits React-compatible code according to their most recent stable release.The JSX namespace
Similar to how we have special types like
Array
andNumber
in the global namespace to represent the types of[]
and42
, we will add a new global optional namespaceJSX
with some special types in it.This namespace has four special types in it. Let's examine them.
JSX.Element
The type of any
<expr />
isJSX.Element
:If this type does not exist, it is instead an implicit
any
.JSX.IntrinsicElements
When checking an expression of the form
<foo ...>
wherefoo
begins with a lower-case letter, the typeJSX.IntrinsicElements
is examined. If that type has a property calledfoo
, or a string indexer, then the element attributes type (see below) is the type of that property (or the type of that string indexer). This process is called intrinsic lookup.JSX.ElementClass
For expressions of the form
<Foo ... />
or<namespace.foo .../>
we use to value-based lookup: Given an expression<Foo ... >
, find a value namedFoo
in the current lexical scope.If no value with this name exists, an error is issued. Otherwise, we treat the type of
foo
as an element class. The element class is the constructor or factory function that produces an instance of an element instance type.It is an error if the element class is not assignable to the type
JSX.ElementClass
, or if value-based lookup occurs and no type namedJSX.ElementClass
exists.The element instance type is the return types of the first construct signatures (if present), or the first call signature (if no construct signatures are present). If the element type is
any
, the element instance type isany
.It is an error if the element class has no call or construct signatures.
JSX.ElementAttributesProperty
Given an element instance type, we need to produce a type that lists the allowed attributes for that element. We call this the element attributes type. For example, in a React element
<div>
, the element attributes type includes properties likeid
,className
, andonClick
.The interface
JSX.ElementAttributesProperty
defines this process. It may have 0 properties, in which case all attributes are assumed to be valid and of typeany
, or 1 property, in which case the attributes of the element must map to the properties of the type of that property.Note that intrinsic lookup is not affected by
ElementClass
orElementAttributesProperty
.Attribute Checking
Given an element attributes type
E
derived fromSomething
in the above example, the attributes of the element are checked as follows:P
initialized withexpr
:E
has a propertyP
, processexpr
with the contextual type of the type ofE.P
. Otherwise, issue an error.expr
is not assignable toE.P
.P
to the list of properties seenexpr:
P
in the apparent type ofspr
:P
occurs (either as an explicit attribute, as shown above withy
, or via another spread attribute), nothing happensE
has a propertyP
:spr.P
is not assignable toE.P
.P
to the list of properties seenE
does not have a corresponding entry in the list of properties seenNon-identifier attributes
If an attribute name would not be a valid identifier in JavaScript code, there need not be a corresponding property in the element attributes type. This exception does not apply to names that are invalid identifiers because they are reserved words (like
var
).The text was updated successfully, but these errors were encountered: