TypeScript 中文网: 文档
JSX 是一种可嵌入的类 XML 语法。它旨在转换为有效的 JavaScript,尽管该转换的语义是特定于实现的。JSX 随着 React 框架而流行起来,但后来也看到了其他实现。TypeScript 支持嵌入、类型检查和将 JSX 直接编译为 JavaScript。
¥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
为了使用 JSX,你必须做两件事。
¥In order to use JSX you must do two things.
使用 .tsx 扩展名命名你的文件
¥Name your files with a .tsx extension
启用 jsx 选项
¥Enable the jsx option
TypeScript 附带几种 JSX 模式:preserve、react(经典运行时)、react-jsx(自动运行时)、react-jsxdev(自动开发运行时)和 react-native。preserve 模式将保持 JSX 作为输出的一部分,以供另一个转换步骤(例如 Babel)进一步使用。此外,输出将具有 .jsx 文件扩展名。react 模式会触发 React.createElement,使用前不需要经过 JSX 转换,输出会有 .js 文件扩展名。react-native 模式等价于 preserve,因为它保留所有 JSX,但输出将改为具有 .js 文件扩展名。
¥TypeScript ships with several JSX modes: preserve, react (classic runtime), react-jsx (automatic runtime), react-jsxdev (automatic development runtime), and react-native.
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.
模式
输入
输出
输出文件扩展名
preserve
.jsx
react
React.createElement("div")
.js
react-native
.js
react-jsx
_jsx("div", {}, void 0);
.js
react-jsxdev
_jsxDEV("div", {}, void 0, false, {...}, this);
.js
你可以使用 jsx 命令行标志或相应的选项 tsconfig.json 中的 jsx 文件指定此模式。
¥You can specify this mode using either the jsx command line flag or the corresponding option jsx in your tsconfig.json file.
*注意:你可以使用 jsxFactory 选项指定 JSX 工厂函数(默认为 React.createElement)
¥*Note: You can specify the JSX factory function to use when targeting react JSX emit with jsxFactory option (defaults to React.createElement)
as 运算符
¥The as operator
回想一下如何编写类型断言:
¥Recall how to write a type assertion:
tsconst foo =
这将变量 bar 断言为具有 Foo 类型。由于 TypeScript 也使用尖括号来进行类型断言,将它与 JSX 的语法结合起来会带来一定的解析困难。因此,TypeScript 不允许在 .tsx 文件中使用尖括号类型断言。
¥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.
由于上述语法不能在 .tsx 文件中使用,因此应使用替代类型断言运算符:as。该示例可以很容易地用 as 运算符重写。
¥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.
tsconst foo = bar as Foo;
as 运算符在 .ts 和 .tsx 文件中都可用,并且在行为上与尖括号类型断言样式相同。
¥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
为了理解 JSX 的类型检查,你必须首先了解内在元素和基于值的元素之间的区别。给定一个 JSX 表达式
¥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
This is important for two reasons:
对于 React,内在元素作为字符串 (React.createElement("div")) 触发,而你创建的组件不是 (React.createElement(MyComponent))。
¥For React, intrinsic elements are emitted as strings (React.createElement("div")), whereas a component you’ve created is not (React.createElement(MyComponent)).
在 JSX 元素中传递的属性类型应该以不同的方式查找。内在元素属性应该是内在已知的,而组件可能希望指定它们自己的属性集。
¥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 使用 与 React 相同的约定 来区分这些。内在元素总是以小写字母开头,而基于值的元素总是以大写字母开头。
¥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.
JSX 命名空间
¥The JSX namespace
TypeScript 中的 JSX 由 JSX 命名空间键入。JSX 命名空间可以在各个地方定义,具体取决于 jsx 编译器选项。
¥JSX in TypeScript is typed by the JSX namespace. The JSX namespace may be defined in various places, depending on the jsx compiler option.
jsx 选项 preserve、react 和 react-native 使用经典运行时的类型定义。这意味着变量需要在由 jsxFactory 编译器选项确定的范围内。JSX 命名空间应在 JSX 工厂的最顶层标识符上指定。例如,React 使用默认工厂 React.createElement。这意味着它的 JSX 命名空间应定义为 React.JSX。
¥The jsx options preserve, react, and react-native use the type definitions for classic runtime. This means a variable needs to be in scope that’s determined by the jsxFactory compiler option. The JSX namespace should be specified on the top-most identifier of the JSX factory. For example, React uses the default factory React.createElement. This means its JSX namespace should be defined as React.JSX.
tsexport function createElement(): any;export namespace JSX { // …}
并且用户应始终将 React 导入为 React。
¥And the user should always import React as React.
tsimport * as React from 'react';
Preact 使用 JSX 工厂 h。这意味着它的类型应该定义为 h.JSX。
¥Preact uses the JSX factory h. That means its types should be defined as the h.JSX.
tsexport function h(props: any): any;export namespace h.JSX { // …}
用户应使用命名导入来导入 h。
¥The user should use a named import to import h.
tsimport { h } from 'preact';
对于 jsx 选项 react-jsx 和 react-jsxdev,应从匹配的入口点导出 JSX 命名空间。对于 react-jsx,这是 ${jsxImportSource}/jsx-runtime。对于 react-jsxdev,这是 ${jsxImportSource}/jsx-dev-runtime。由于这些不使用文件扩展名,因此必须在 package.json 映射中使用 exports 字段才能支持 ESM 用户。
¥For the jsx options react-jsx and react-jsxdev, the JSX namespace should be exported from the matching entry points. For react-jsx this is ${jsxImportSource}/jsx-runtime. For react-jsxdev, this is ${jsxImportSource}/jsx-dev-runtime. Since these don’t use a file extension, you must use the exports field in package.json map in order to support ESM users.
json{ "exports": { "./jsx-runtime": "./jsx-runtime.js", "./jsx-dev-runtime": "./jsx-dev-runtime.js", }}
然后在 jsx-runtime.d.ts 和 jsx-dev-runtime.d.ts 中:
¥Then in jsx-runtime.d.ts and jsx-dev-runtime.d.ts:
tsexport namespace JSX { // …}
请注意,虽然导出 JSX 命名空间足以进行类型检查,但生产运行时在运行时需要 jsx、jsxs 和 Fragment 导出,而开发运行时需要 jsxDEV 和 Fragment。理想情况下,你也为这些添加类型。
¥Note that while exporting the JSX namespace is sufficient for type checking, the production runtime needs the jsx, jsxs, and Fragment exports at runtime, and the development runtime needs jsxDEV and Fragment. Ideally you add types for those too.
如果 JSX 命名空间在适当位置不可用,则经典和自动运行时都会回退到全局 JSX 命名空间。
¥If the JSX namespace isn’t available in the appropriate location, both the classic and the automatic runtime fall back to the global JSX namespace.
内在要素
¥Intrinsic elements
在特殊接口 JSX.IntrinsicElements 上查找内在元素。默认情况下,如果未指定此接口,则任何事情都会发生,并且不会对内部元素进行类型检查。但是,如果此接口存在,则内部元素的名称将作为 JSX.IntrinsicElements 接口上的属性进行查找。例如:
¥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:
tsxdeclare namespace JSX { interface IntrinsicElements { foo: any; }}
在上面的示例中,
¥In the above example,
注意:你还可以在 JSX.IntrinsicElements 上指定一个包罗万象的字符串索引器,如下所示:
¥Note: You can also specify a catch-all string indexer on JSX.IntrinsicElements as follows:
tsdeclare namespace JSX { interface IntrinsicElements { [elemName: string]: any; }}
基于值的元素
¥Value-based elements
基于值的元素只需通过作用域内的标识符进行查找。
¥Value-based elements are simply looked up by identifiers that are in scope.
tsximport MyComponent from "./myComponent";
有两种方法可以定义基于值的元素:
¥There are two ways to define a value-based element:
函数组件 (FC)
¥Function Component (FC)
类组件
¥Class Component
因为这两种类型的基于值的元素在 JSX 表达式中彼此无法区分,所以首先 TS 尝试使用重载解析将表达式解析为函数组件。如果该过程成功,则 TS 完成将表达式解析为其声明。如果该值无法解析为函数组件,则 TS 将尝试将其解析为类组件。如果失败,TS 将报告错误。
¥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
顾名思义,该组件被定义为一个 JavaScript 函数,其第一个参数是一个 props 对象。TS 强制其返回类型必须可分配给 JSX.Element。
¥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.
tsxinterface FooProp { name: string; X: number; Y: number;}declare function AnotherComponent(prop: { name: string });function ComponentFoo(prop: FooProp) { return
因为函数组件只是一个 JavaScript 函数,所以这里也可以使用函数重载:
¥Because a Function Component is simply a JavaScript function, function overloads may be used here as well:
tsinterface ClickableProps { children: JSX.Element[] | JSX.Element;} interface HomeProps extends ClickableProps { home: JSX.Element;} interface SideProps extends ClickableProps { side: JSX.Element | string;} function MainButton(prop: HomeProps): JSX.Element;function MainButton(prop: SideProps): JSX.Element;function MainButton(prop: ClickableProps): JSX.Element { // ...}Try
注意:函数组件以前称为无状态函数组件 (SFC)。由于函数组件在最近版本的 react 中不再被认为是无状态的,所以类型 SFC 及其别名 StatelessComponent 已被弃用。
¥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 alias StatelessComponent 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
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.
一旦建立了类类型,实例类型由类类型构造的返回类型或调用签名(以存在者为准)的联合确定。同样,在 ES6 类的情况下,实例类型将是该类的实例的类型,而在工厂函数的情况下,它将是从函数返回的值的类型。
¥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.
tsclass MyComponent { render() {}}// use a construct signatureconst myComponent = new MyComponent();// element class type => MyComponent// element instance type => { render: () => void }function MyFactoryFunction() { return { render: () => {}, };}// use a call signatureconst myComponent = MyFactoryFunction();// element class type => MyFactoryFunction// element instance type => { render: () => void }
元素实例类型很有趣,因为它必须可以分配给 JSX.ElementClass,否则会导致错误。默认情况下 JSX.ElementClass 是 {},但可以扩展它以将 JSX 的使用限制为仅符合正确接口的那些类型。
¥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.
tsxdeclare namespace JSX { interface ElementClass { render: any; }}class MyComponent { render() {}}function MyFactoryFunction() { return { render: () => {} };}
属性类型检查
¥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.
对于内在元素,它是 JSX.IntrinsicElements 上的属性类型
¥For intrinsic elements, it is the type of the property on JSX.IntrinsicElements
tsxdeclare namespace JSX { interface IntrinsicElements { foo: { bar?: boolean }; }}// element attributes type for 'foo' is '{bar?: boolean}'
对于基于值的元素,它有点复杂。它由先前确定的元素实例类型上的属性类型确定。使用哪个属性由 JSX.ElementAttributesProperty 决定。它应该用一个属性声明。然后使用该属性的名称。从 TypeScript 2.8 开始,如果未提供 JSX.ElementAttributesProperty,则将使用类元素的构造函数或函数组件调用的第一个参数的类型。
¥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.
tsxdeclare namespace JSX { interface ElementAttributesProperty { props; // specify the property name to use }}class MyComponent { // specify the property on the element instance type props: { foo?: string; };}// element attributes type for 'MyComponent' is '{foo?: string}'
element 属性类型用于对 JSX 中的属性进行类型检查。支持可选和必需的属性。
¥The element attribute type is used to type check the attributes in the JSX.
Optional and required properties are supported.
tsxdeclare namespace JSX { interface IntrinsicElements { foo: { requiredProp: string; optionalProp?: number }; }}
注意:如果属性名称不是有效的 JS 标识符(如 data-* 属性),如果在元素属性类型中找不到它,则不认为是错误。
¥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.
此外,JSX.IntrinsicAttributes 接口可用于指定 JSX 框架使用的额外属性,这些属性通常不用于组件的 props 或参数 - 例如 React 中的 key。进一步专门化,泛型 JSX.IntrinsicClassAttributes
¥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
扩展运算符也适用:
¥The spread operator also works:
tsxconst props = { requiredProp: "bar" };
子类型检查
¥Children Type Checking
在 TypeScript 2.3 中,TS 引入了子项的类型检查。children 是元素属性类型中的一个特殊属性,其中子 JSXExpressions 被插入到属性中。类似于 TS 使用 JSX.ElementAttributesProperty 来确定 props 的名称,TS 使用 JSX.ElementChildrenAttribute 来确定这些 props 中的子级的名字。JSX.ElementChildrenAttribute 应使用单个属性声明。
¥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.
tsdeclare namespace JSX { interface ElementChildrenAttribute { children: {}; // specify children name to use }}
tsx
Hello
Hello
World你可以像任何其他属性一样指定子项的类型。这将覆盖默认类型,例如 React 类型,如果你使用它们。
¥You can specify the type of children like any other attribute. This will override the default type from, e.g. the React typings if you use them.
tsxinterface PropsType { children: JSX.Element name: string}class Component extends React.Component {this.props.children}
) }}// OKHello World
Hello World
Hello World
Hello
World
JSX 结果类型
¥The JSX result type
默认情况下,JSX 表达式的结果类型为 any。你可以通过指定 JSX.Element 接口来自定义类型。但是,无法从此接口检索有关 JSX 的元素、属性或子项的类型信息。这是一个黑匣子。
¥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.
JSX 函数返回类型
¥The JSX function return type
默认情况下,函数组件必须返回 JSX.Element | null。但是,这并不总是代表运行时行为。从 TypeScript 5.1 开始,你可以指定 JSX.ElementType 来覆盖有效的 JSX 组件类型。请注意,这并未定义哪些 props 是有效的。props 的类型始终由传递的组件的第一个参数定义。默认值如下所示:
¥By default, function components must return JSX.Element | null. However, this doesn’t always represent runtime behaviour. As of TypeScript 5.1, you can specify JSX.ElementType to override what is a valid JSX component type. Note that this doesn’t define what props are valid. The type of props is always defined by the first argument of the component that’s passed. The default looks something like this:
tsnamespace JSX { export type ElementType = // All the valid lowercase tags | keyof IntrinsicElements // Function components | (props: any) => Element // Class components | new (props: any) => ElementClass; export interface IntrinsicAttributes extends /*...*/ {} export type Element = /*...*/; export type ElementClass = /*...*/;}
嵌入表达式
¥Embedding Expressions
JSX 允许你通过用大括号 ({ }) 包围表达式来在标签之间嵌入表达式。
¥JSX allows you to embed expressions between tags by surrounding the expressions with curly braces ({ }).
tsxconst a = (
上面的代码将导致错误,因为你不能将字符串除以数字。使用 preserve 选项时的输出如下所示:
¥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:
tsxconst a = (
React 集成
¥React integration
要将 JSX 与 React 一起使用,你应该使用 React 类型。这些类型定义了 JSX 命名空间,以便与 React 一起使用。
¥To use JSX with React you should use the React typings.
These typings define the JSX namespace appropriately for use with React.
tsx///
配置 JSX
¥Configuring JSX
有多个编译器标志可用于自定义 JSX,它们既可用作编译器标志,也可通过内联的每个文件编译指示工作。要了解更多信息,请参阅他们的 tsconfig 参考页面:
¥There are multiple compiler flags which can be used to customize your JSX, which work as both a compiler flag and via inline per-file pragmas. To learn more see their tsconfig reference pages:
jsxFactory
jsxFragmentFactory
jsxImportSource