TypeScript 中文网: 文档

2025-10-15 23:44:48

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;

这将变量 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 表达式 ,expr 可以指代环境固有的东西(例如 DOM 环境中的 div 或 span),也可以指你创建的自定义组件。这很重要,原因有两个:

¥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 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:

对于 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; }}; // ok; // error

在上面的示例中, 可以正常工作,但 将导致错误,因为它没有在 JSX.IntrinsicElements 上指定。

¥In the above example, will work fine but will result in an error since it has not been specified on JSX.IntrinsicElements.

注意:你还可以在 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";; // ok; // error

有两种方法可以定义基于值的元素:

¥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 ;}const Button = (prop: { value: string }, context: { color: string }) => (