JSX (Full)
https://jsx.github.io/doc/tutorial.html
Đọc thêm:
https://www.typescriptlang.org/docs/handbook/jsx.html
Background
JSX is a statically-typed, object-oriented programming language compiling to standalone JavaScript. The reason why JSX was developed is our need for a more robust programming language than JavaScript. JSX is, however, fairly close to JavaScript especially in its statements and expressions.
Statically-typed programming language is robust because certain sorts of problems, for example typos in variable names or missing function definitions, are detected at compile-time. This is important especially in middle- to large-scale software development in which a number of engineers may be engaged.
Therefore, JSX is designed as a statically-typed language. All the values and variables have a static type and you can only assign a correctly-typed value to a variable. In addition, all the functions including closures have types which are determined by the types of parameters and the return values, where you cannot call a function with incorrectly typed arguments.
Also, another important reason why JSX was developed is to boost JavaScript performance. JavaScript itself is not so slow but large-scale development tends to have many abstraction layers, e.g. proxy classes and accessor methods, which often have negative impact on performance. JSX boosts performance by inline expansion: function bodies are expanded to where they are being called, if the functions being called could be determined at compile-time. This is the power of the statically-typed language in terms of performance.
Run "Hello, World!"
Let's start by running our first JSX program: hello.jsx
. We use the jsx
command, which is the JSX compiler in the JSX distribution, to compile JSX source code to JavaScript.
First, install jsx with npm:
Then, make the code below as hello.jsx
:
Now you can run a JSX program with the following command and you will get Hello, world
on the console.
We will look into the detail of hello.jsx
in the next section.
Program Structure
Here is hello.jsx
, the source code of the "Hello world!" example. You can see several features of JSX in this program, namely, static types and class structure within the source code.
Class _Main
has a static member function (a.k.a. a class method) named main
, that takes an array of strings and returns nothing. _Main.main(:string[]):void
is the entry point of JSX applications that is called when a user invokes an application from command line. JSX, like Java, does not allow top-level statements or functions.
The log
statement is mapped to console.log()
in JavaScript, which displays the arguments to stdout with a newline.
Next, we look into another typical library class, Point
:
As you can see, member variables of Point, var x
and var y
, are declared without types, but their types are deducted from their initial values to be number
.
You might be surprised at multiple definition of constructors: one takes no parameters and the others take parameters. They are overloaded by their types of parameters. When you construct the class with new Point()
, the first constructor, which takes no parameters, is called. The second with two parameters will be called on new Point(2, 3)
and the third with one parameter will be called as a copy constructor. Other forms of construction, e.g. new Point(42)
or new Point("foo", "bar")
will cause compilation errors of mismatching signatures. The Point#set()
functions are also overloaded and the compiler know how to call the correct one.
Static Typing
Basic type concept will be described in this section. Primitive types, object types, variant type, and Nullable types exist in JSX.
Pritimive Types
There are three pritimive types in JSX: string
, number
, and boolean
. The three are non-nullable, immutable types. The code snippet below declares three variables s
, n
, b
with their repective types, annocated to the right of the name of the variables using the :
mark.
Type annotations can be omitted when a variable is initialized at the same moment of declaration.
Object Types
Object types are types of values to hold reference to objects - which are instances of classes. For example, functions, string[]
(array of strings), Date
are all object types. Whether they are mutable or not depends on the definition of each class.
Most of the objects (values of object types) are constructed using the new
operator.
Array
and Map
types can also be instatiated by using their initializer expressions.
Variables of the Function
class can only be instantiated as a static function or by using function expression or function statement (the details are described laterwards).
The Variant Type
Variant type, which means "no static type information," is useful for interacting with existing JavaScript APIs. Some JavaScript libraries may return a variant value, which type cannot be determined at compile time. All you can do on variant values is to check equality of a variant value to another variant value. You have to cast it to another type before doing anything else on the value.
Nullable Types
Nullable type is a meta type which indicates a value may be null. It is prohibited to assign null
to the primitive types (note: Object types are nullable by default). Nullable
types should instead be used for such purposes.
The most prominent use case of Nullable
types is when interacting with an array. For example, an out-of-bounds access to an array returns null
.
There are APIs that return Nullable
types also exists. For example, the return type of Array.<string>#shift()
is Nullable.<string>
. When you use a Nullable value, you have to make sure of the value is not null.
When the source code is compiled in debug mode (which is the default), the compiler will insert run-time type-checking code. An exception will be raised (or the debugger will be activated) when misuse of a null value as actual value is detected. Run-time type checks can be omitted by compiling the source code with the --release
option.
Please refer to the Types section of the language reference for more information.
Expressions
The definitions of operators in JSX are almost equivalent to JavaScript, however there are few exceptions.
arithmetic operators (
+
,-
,*
,/
...) only accept numbers as the operandsNote: concatenation operator
+
exists for concatenation of stringsthe dot property accessor can only access the defined properties
the [] property accessor can only be applied to values of type
Map
orvariant
introduction of the
as
operator, used for type conversion between primitive types / casting object typeslogical operators (
&&
,||
) returns boolean, and the introduction of binary?:
operator as the equivalent to the||
operator of JavaScript
A complete list of operators can be found in the Operator Reference.
Statements
JSX supports most of the statement types provided by JavaScript. The exceptions are:
log statement
assert statement
try-catch-finally statement
no
with
statement
A complete list of statements can be found in the Statement Reference.
Classes and Interfaces
JSX is a class-based object-oriented language, and its class model is similar to Java.
a class may extend another class (single inheritance)
a class may implement multiple interfaces and mixins
all classes share a single root class: the
Object
class
In the example, the Bat class extends the Animal class, so it inherits the Animal#eat()
member function, and it can be assigned to a variable typed to Animal. The class also implements the Flyable
interface overriding the Flyable#fly()
member function, so it can be assigned to a variable typed Flyable
. There's also another flyable class, Bee
. By using the Flyable
interface, it is possible to deal with both classes as a flyable being, even if the organ of a bee is completely different from that of a bat.When overriding a member function, the use the override
keyword is mandatory. Otherwise the compiler will report an error. In other words, you are saved from unexpected interface changes in the base classes which cause compilation errors in derived classes instead of undesirable runtime errors.
Functions and Closures
In JSX, functions are first-class objects and they have static types. You can declare a variable of a function type like var f : function(arg : number) : number
, a function that takes a number as an argument and returns another number (or, just returns the same value as the argument; but it's not important here). The variable f
can be called as f(42)
from which you will get a number value.It is possible to define closures using the function
expression or the function
statement. They are typically used to implement callbacks ore event listeners which are popular in GUI programming. Closures are similar to JavaScript except for what this
points at: when a closure is defined within a member function, it refers to the receiver of the member function. See the following example.
Type annocations of function expressions / statements may be omitted if they can be inferred by the compiler. In the exmaple below, both the type of the argument n
and the return type of the function expression is inferred from the definition of Array#map
to be number
.
Modules
JSX has a module system. You can use JSX class libraries by the import
statement. For example, the following program uses timer.jsx
module, which exports the Timer
class.
A module may export multiple classes, but you can specify what modules you import or name a namespace which the module is imported into.
Interface to Web Browsers
The js/web.jsx
module provides the interface to web browser APIs, e.g. the window
object and DOM APIs. The example below shows how to insert a text node into an HTML.
Once you compile hello.jsx
by the following command, then you can access the HTML and you will see it saying "Hello, world!."
Further Learning
More documents can be found on the wiki.If you are looking for examples, please refer to the examples on this web site, the example
directory of the distribution, or to the links on Resources page of the wiki.
Đọc thêm
JSX is an embeddable XML-like syntax. It is meant to be transformed into valid JavaScript, though the semantics of that transformation are implementation-specific. JSX rose to popularity with the React framework, but has since seen other implementations as well. TypeScript supports embedding, type checking, and compiling JSX directly to JavaScript.
Basic usage
In order to use JSX you must do two things.
Name your files with a
.tsx
extensionEnable the
jsx
option
TypeScript ships with three JSX modes: preserve
, react
, and react-native
. These modes only affect the emit stage - type checking is unaffected. The preserve
mode will keep the JSX as part of the output to be further consumed by another transform step (e.g. Babel). Additionally the output will have a .jsx
file extension. The react
mode will emit React.createElement
, does not need to go through a JSX transformation before use, and the output will have a .js
file extension. The react-native
mode is the equivalent of preserve
in that it keeps all JSX, but the output will instead have a .js
file extension.
Mode
Input
Output
Output File Extension
preserve
<div />
<div />
.jsx
react
<div />
React.createElement("div")
.js
react-native
<div />
<div />
.js
You can specify this mode using either the --jsx
command line flag or the corresponding option in your tsconfig.json file.
*Note: You can specify the JSX factory function to use when targeting react JSX emit with
--jsxFactory
option (defaults toReact.createElement
)
The as
operator
as
operatorRecall how to write a type assertion:
This asserts the variable bar
to have the type foo
. Since TypeScript also uses angle brackets for type assertions, combining it with JSX’s syntax would introduce certain parsing difficulties. As a result, TypeScript disallows angle bracket type assertions in .tsx
files.
Since the above syntax cannot be used in .tsx
files, an alternate type assertion operator should be used: as
. The example can easily be rewritten with the as
operator.
The as
operator is available in both .ts
and .tsx
files, and is identical in behavior to the angle-bracket type assertion style.
Type Checking
In order to understand type checking with JSX, you must first understand the difference between intrinsic elements and value-based elements. Given a JSX expression <expr />
, expr
may either refer to something intrinsic to the environment (e.g. a div
or span
in a DOM environment) or to a custom component that you’ve created. This is important for two reasons:
For React, intrinsic elements are emitted as strings (
React.createElement("div")
), whereas a component you’ve created is not (React.createElement(MyComponent)
).The types of the attributes being passed in the JSX element should be looked up differently. Intrinsic element attributes should be known intrinsically whereas components will likely want to specify their own set of attributes.
TypeScript uses the same convention that React does for distinguishing between these. An intrinsic element always begins with a lowercase letter, and a value-based element always begins with an uppercase letter.
Intrinsic elements
Intrinsic elements are looked up on the special interface JSX.IntrinsicElements
. By default, if this interface is not specified, then anything goes and intrinsic elements will not be type checked. However, if this interface is present, then the name of the intrinsic element is looked up as a property on the JSX.IntrinsicElements
interface. For example:
In the above example, <foo />
will work fine but <bar />
will result in an error since it has not been specified on JSX.IntrinsicElements
.
Note: You can also specify a catch-all string indexer on
JSX.IntrinsicElements
as follows:
Value-based elements
Value-based elements are simply looked up by identifiers that are in scope.
There are two ways to define a value-based element:
Function Component (FC)
Class Component
Because these two types of value-based elements are indistinguishable from each other in a JSX expression, first TS tries to resolve the expression as a Function Component using overload resolution. If the process succeeds, then TS finishes resolving the expression to its declaration. If the value fails to resolve as a Function Component, TS will then try to resolve it as a class component. If that fails, TS will report an error.
Function Component
As the name suggests, the component is defined as a JavaScript function where its first argument is a props
object. TS enforces that its return type must be assignable to JSX.Element
.
Because a Function Component is simply a JavaScript function, function overloads may be used here as well:
Note: Function Components were formerly known as Stateless Function Components (SFC). As Function Components can no longer be considered stateless in recent versions of react, the type
SFC
and its aliasStatelessComponent
were deprecated.
Class Component
It is possible to define the type of a class component. However, to do so it is best to understand two new terms: the element class type and the element instance type.
Given <Expr />
, the element class type is the type of Expr
. So in the example above, if MyComponent
was an ES6 class the class type would be that class’s constructor and statics. If MyComponent
was a factory function, the class type would be that function.
Once the class type is established, the instance type is determined by the union of the return types of the class type’s construct or call signatures (whichever is present). So again, in the case of an ES6 class, the instance type would be the type of an instance of that class, and in the case of a factory function, it would be the type of the value returned from the function.
The element instance type is interesting because it must be assignable to JSX.ElementClass
or it will result in an error. By default JSX.ElementClass
is {}
, but it can be augmented to limit the use of JSX to only those types that conform to the proper interface.
Attribute type checking
The first step to type checking attributes is to determine the element attributes type. This is slightly different between intrinsic and value-based elements.
For intrinsic elements, it is the type of the property on JSX.IntrinsicElements
For value-based elements, it is a bit more complex. It is determined by the type of a property on the element instance type that was previously determined. Which property to use is determined by JSX.ElementAttributesProperty
. It should be declared with a single property. The name of that property is then used. As of TypeScript 2.8, if JSX.ElementAttributesProperty
is not provided, the type of first parameter of the class element’s constructor or Function Component’s call will be used instead.
The element attribute type is used to type check the attributes in the JSX. Optional and required properties are supported.
Note: If an attribute name is not a valid JS identifier (like a
data-*
attribute), it is not considered to be an error if it is not found in the element attributes type.
Additionally, the JSX.IntrinsicAttributes
interface can be used to specify extra properties used by the JSX framework which are not generally used by the components’ props or arguments - for instance key
in React. Specializing further, the generic JSX.IntrinsicClassAttributes<T>
type may also be used to specify the same kind of extra attributes just for class components (and not Function Components). In this type, the generic parameter corresponds to the class instance type. In React, this is used to allow the ref
attribute of type Ref<T>
. Generally speaking, all of the properties on these interfaces should be optional, unless you intend that users of your JSX framework need to provide some attribute on every tag.
The spread operator also works:
Children Type Checking
In TypeScript 2.3, TS introduced type checking of children. children is a special property in an element attributes type where child JSXExpressions are taken to be inserted into the attributes. Similar to how TS uses JSX.ElementAttributesProperty
to determine the name of props, TS uses JSX.ElementChildrenAttribute
to determine the name of children within those props. JSX.ElementChildrenAttribute
should be declared with a single property.
You can specify the type of children like any other attribute. This will override the default type from, eg the React typings if you use them.
The JSX result type
By default the result of a JSX expression is typed as any
. You can customize the type by specifying the JSX.Element
interface. However, it is not possible to retrieve type information about the element, attributes or children of the JSX from this interface. It is a black box.
Embedding Expressions
JSX allows you to embed expressions between tags by surrounding the expressions with curly braces ({ }
).
The above code will result in an error since you cannot divide a string by a number. The output, when using the preserve
option, looks like:
React integration
To use JSX with React you should use the React typings. These typings define the JSX
namespace appropriately for use with React.
Factory Functions
The exact factory function used by the jsx: react
compiler option is configurable. It may be set using either the jsxFactory
command line option, or an inline @jsx
comment pragma to set it on a per-file basis. For example, if you set jsxFactory
to createElement
, <div />
will emit as createElement("div")
instead of React.createElement("div")
.
The comment pragma version may be used like so (in TypeScript 2.8):
emits as:
The factory chosen will also affect where the JSX
namespace is looked up (for type checking information) before falling back to the global one. If the factory is defined as React.createElement
(the default), the compiler will check for React.JSX
before checking for a global JSX
. If the factory is defined as h
, it will check for h.JSX
before a global JSX
.
Last updated
Was this helpful?