JSX

JSX 是一種可嵌入的類似 XML 的語法。它旨在轉換為有效的 JavaScript,儘管該轉換的語義是特定於實現的。JSX 隨著 React 框架的流行而興起,但此後也出現了其他實現。TypeScript 支援嵌入、型別檢查並將 JSX 直接編譯為 JavaScript。

基本用法

要使用 JSX,必須完成兩件事。

  1. 將檔案命名為 .tsx 副檔名
  2. 啟用 jsx 選項

TypeScript 附帶了多種 JSX 模式:preservereact(經典執行時)、react-jsx(自動執行時)、react-jsxdev(自動開發執行時)和 react-nativepreserve 模式將 JSX 保留在輸出中,以便後續透過轉換步驟(例如 Babel)進行處理。此外,輸出檔案的副檔名為 .jsxreact 模式會生成 React.createElement,無需在透過 JSX 轉換後即可使用,且輸出檔案的副檔名為 .jsreact-native 模式等同於 preserve,它保留所有 JSX,但輸出檔案的副檔名為 .js

模式 輸入 輸出 輸出副檔名
preserve <div /> <div /> .jsx
react <div /> React.createElement("div") .js
react-native <div /> <div /> .js
react-jsx <div /> _jsx("div", {}, void 0); .js
react-jsxdev <div /> _jsxDEV("div", {}, void 0, false, {...}, this); .js

你可以使用 jsx 命令列標誌或 tsconfig.json 檔案中的 jsx 選項來指定此模式。

*注意:在使用 react JSX 生成時,你可以透過 jsxFactory 選項指定要使用的 JSX 工廠函式(預設為 React.createElement

as 運算子

回顧如何編寫型別斷言

ts
const foo = <Foo>bar;

這斷言變數 bar 具有 Foo 型別。由於 TypeScript 也使用尖括號進行型別斷言,將其與 JSX 的語法結合使用會導致某些解析困難。因此,TypeScript 禁止在 .tsx 檔案中使用尖括號型別斷言。

由於上述語法不能在 .tsx 檔案中使用,因此應使用另一種型別斷言運算子:as。該示例可以輕鬆地用 as 運算子重寫。

ts
const foo = bar as Foo;

as 運算子可在 .ts.tsx 檔案中使用,其行為與尖括號型別斷言風格相同。

型別檢查

為了理解 JSX 的型別檢查,必須首先理解固有元素(intrinsic elements)和基於值的元素(value-based elements)之間的區別。對於給定的 JSX 表示式 <expr />expr 可能指代環境中的固有內容(例如 DOM 環境中的 divspan),或者指代你建立的自定義元件。這一點之所以重要,有兩個原因:

  1. 對於 React,固有元素作為字串生成(React.createElement("div")),而你建立的元件則不是(React.createElement(MyComponent))。
  2. JSX 元素中傳遞的屬性型別查詢方式應當不同。固有元素的屬性應該是固有已知的,而元件通常需要指定它們自己的一組屬性。

TypeScript 使用了與 React 相同的慣例來區分這些元素。固有元素始終以小寫字母開頭,而基於值的元素始終以大寫字母開頭。

JSX 名稱空間

TypeScript 中的 JSX 由 JSX 名稱空間定義。根據 jsx 編譯器選項的不同,JSX 名稱空間可能定義在不同的地方。

jsx 選項 preservereactreact-native 使用經典執行時的型別定義。這意味著需要一個由 jsxFactory 編譯器選項確定的變數在作用域內。JSX 名稱空間應在 JSX 工廠的最頂層識別符號上指定。例如,React 使用預設工廠 React.createElement。這意味著它的 JSX 名稱空間應定義為 React.JSX

ts
export function createElement(): any;
export namespace JSX {
// …
}

使用者應始終將 React 匯入為 React

ts
import * as React from 'react';

Preact 使用 JSX 工廠 h。這意味著它的型別應定義為 h.JSX

ts
export function h(props: any): any;
export namespace h.JSX {
// …
}

使用者應使用命名匯入來匯入 h

ts
import { h } from 'preact';

對於 jsx 選項 react-jsxreact-jsxdevJSX 名稱空間應從匹配的入口點匯出。對於 react-jsx,這是 ${jsxImportSource}/jsx-runtime。對於 react-jsxdev,這是 ${jsxImportSource}/jsx-dev-runtime。由於這些不使用副檔名,為了支援 ESM 使用者,你必須在 package.jsonexports 欄位中進行對映。

json
{
"exports": {
"./jsx-runtime": "./jsx-runtime.js",
"./jsx-dev-runtime": "./jsx-dev-runtime.js",
}
}

然後在 jsx-runtime.d.tsjsx-dev-runtime.d.ts

ts
export namespace JSX {
// …
}

請注意,雖然匯出 JSX 名稱空間足以進行型別檢查,但生產執行時需要在執行時匯出 jsxjsxsFragment,而開發執行時需要 jsxDEVFragment。理想情況下,你也應該為這些新增型別。

如果 JSX 名稱空間在相應位置不可用,經典執行時和自動執行時都會回退到全域性的 JSX 名稱空間。

固有元素

固有元素在特殊的介面 JSX.IntrinsicElements 上進行查詢。預設情況下,如果未指定此介面,則允許使用任何元素,且不會對固有元素進行型別檢查。但是,如果存在此介面,則固有元素的名稱將作為 JSX.IntrinsicElements 介面上的一個屬性進行查詢。例如

tsx
declare namespace JSX {
interface IntrinsicElements {
foo: any;
}
}
<foo />; // ok
<bar />; // error

在上面的示例中,<foo /> 可以正常工作,但 <bar /> 將導致錯誤,因為它未在 JSX.IntrinsicElements 上指定。

注意:你也可以在 JSX.IntrinsicElements 上指定一個通用的字串索引簽名,如下所示

ts
declare namespace JSX {
interface IntrinsicElements {
[elemName: string]: any;
}
}

基於值的元素

基於值的元素只需透過作用域內的識別符號進行查詢。

tsx
import MyComponent from "./myComponent";
<MyComponent />; // ok
<SomeOtherComponent />; // error

有兩種定義基於值元素的方法:

  1. 函式元件 (FC)
  2. 類元件

由於這兩種型別的基於值元素在 JSX 表示式中無法相互區分,TS 首先會嘗試透過過載解析將該表示式解析為函式元件。如果成功,TS 將完成表示式到其宣告的解析。如果該值無法解析為函式元件,TS 將嘗試將其解析為類元件。如果再次失敗,TS 將報告錯誤。

函式元件

顧名思義,元件被定義為 JavaScript 函式,其第一個引數是 props 物件。TS 強制要求其返回型別必須可賦值給 JSX.Element

tsx
interface FooProp {
name: string;
X: number;
Y: number;
}
declare function AnotherComponent(prop: { name: string });
function ComponentFoo(prop: FooProp) {
return <AnotherComponent name={prop.name} />;
}
const Button = (prop: { value: string }, context: { color: string }) => (
<button />
);

由於函式元件只是一個 JavaScript 函式,因此這裡也可以使用函式過載

ts
interface 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 已被棄用。

類元件

可以定義類元件的型別。然而,要做到這一點,最好理解兩個新術語:元素類型別 (element class type) 和 元素例項型別 (element instance type)。

對於給定的 <Expr />元素類型別就是 Expr 的型別。因此,在上面的例子中,如果 MyComponent 是一個 ES6 類,類型別將是該類的建構函式和靜態成員。如果 MyComponent 是一個工廠函式,類型別將是該函式。

一旦確定了類型別,例項型別就是由該類型別的構造簽名或呼叫簽名的返回型別(以存在的為準)的聯合型別確定的。因此,在 ES6 類的情況下,例項型別將是該類的例項型別;在工廠函式的情況下,它將是該函式返回值的型別。

ts
class MyComponent {
render() {}
}
// use a construct signature
const myComponent = new MyComponent();
// element class type => MyComponent
// element instance type => { render: () => void }
function MyFactoryFunction() {
return {
render: () => {},
};
}
// use a call signature
const myComponent = MyFactoryFunction();
// element class type => MyFactoryFunction
// element instance type => { render: () => void }

元素例項型別非常重要,因為它必須可賦值給 JSX.ElementClass,否則會導致錯誤。預設情況下 JSX.ElementClass{},但可以透過增強它來限制 JSX 的使用,僅限於符合特定介面的型別。

tsx
declare namespace JSX {
interface ElementClass {
render: any;
}
}
class MyComponent {
render() {}
}
function MyFactoryFunction() {
return { render: () => {} };
}
<MyComponent />; // ok
<MyFactoryFunction />; // ok
class NotAValidComponent {}
function NotAValidFactoryFunction() {
return {};
}
<NotAValidComponent />; // error
<NotAValidFactoryFunction />; // error

屬性型別檢查

屬性型別檢查的第一步是確定元素屬性型別。這在固有元素和基於值的元素之間略有不同。

對於固有元素,它是 JSX.IntrinsicElements 上屬性的型別。

tsx
declare namespace JSX {
interface IntrinsicElements {
foo: { bar?: boolean };
}
}
// element attributes type for 'foo' is '{bar?: boolean}'
<foo bar />;

對於基於值的元素,情況稍微複雜一些。它由之前確定的元素例項型別上的某個屬性型別確定。具體使用哪個屬性由 JSX.ElementAttributesProperty 決定。它應宣告為一個包含單個屬性的型別。該屬性的名稱會被使用。從 TypeScript 2.8 開始,如果沒有提供 JSX.ElementAttributesProperty,則將使用類元素建構函式或函式元件呼叫的第一個引數的型別。

tsx
declare 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}'
<MyComponent foo="bar" />;

元素屬性型別用於檢查 JSX 中的屬性。支援可選和必選屬性。

tsx
declare namespace JSX {
interface IntrinsicElements {
foo: { requiredProp: string; optionalProp?: number };
}
}
<foo requiredProp="bar" />; // ok
<foo requiredProp="bar" optionalProp={0} />; // ok
<foo />; // error, requiredProp is missing
<foo requiredProp={0} />; // error, requiredProp should be a string
<foo requiredProp="bar" unknownProp />; // error, unknownProp does not exist
<foo requiredProp="bar" some-unknown-prop />; // ok, because 'some-unknown-prop' is not a valid identifier

注意:如果屬性名稱不是有效的 JS 識別符號(如 data-* 屬性),如果它在元素屬性型別中找不到,則不會被視為錯誤。

此外,JSX.IntrinsicAttributes 介面可用於指定 JSX 框架使用的額外屬性,這些屬性通常不由元件的 props 或引數使用——例如 React 中的 key。進一步細分,泛型 JSX.IntrinsicClassAttributes<T> 型別也可用於僅為類元件(而非函式元件)指定此類額外屬性。在此型別中,泛型引數對應於類例項型別。在 React 中,這用於允許 Ref<T> 型別的 ref 屬性。通常來說,這些介面上的所有屬性都應該是可選的,除非你打算讓你的 JSX 框架的使用者在每個標籤上都必須提供某些屬性。

展開運算子 (spread operator) 也可以使用

tsx
const props = { requiredProp: "bar" };
<foo {...props} />; // ok
const badProps = {};
<foo {...badProps} />; // error

子元素 (children) 型別檢查

在 TypeScript 2.3 中,TS 引入了對子元素 (children) 的型別檢查。children元素屬性型別中的一個特殊屬性,其中子 JSXExpressions 被視為插入到屬性中。類似於 TS 使用 JSX.ElementAttributesProperty 來確定 props 的名稱,TS 使用 JSX.ElementChildrenAttribute 來確定這些 props 中 children 的名稱。JSX.ElementChildrenAttribute 應宣告為一個包含單個屬性的型別。

ts
declare namespace JSX {
interface ElementChildrenAttribute {
children: {}; // specify children name to use
}
}
tsx
<div>
<h1>Hello</h1>
</div>;
<div>
<h1>Hello</h1>
World
</div>;
const CustomComp = (props) => <div>{props.children}</div>
<CustomComp>
<div>Hello World</div>
{"This is just a JS expression..." + 1000}
</CustomComp>

你可以像其他任何屬性一樣指定 children 的型別。如果你使用了 React 型別定義,這將覆蓋其中的預設型別。

tsx
interface PropsType {
children: JSX.Element
name: string
}
class Component extends React.Component<PropsType, {}> {
render() {
return (
<h2>
{this.props.children}
</h2>
)
}
}
// OK
<Component name="foo">
<h1>Hello World</h1>
</Component>
// Error: children is of type JSX.Element not array of JSX.Element
<Component name="bar">
<h1>Hello World</h1>
<h2>Hello World</h2>
</Component>
// Error: children is of type JSX.Element not array of JSX.Element or string.
<Component name="baz">
<h1>Hello</h1>
World
</Component>

JSX 結果型別

預設情況下,JSX 表示式的結果被標記為 any。你可以透過指定 JSX.Element 介面來自定義該型別。然而,無法從該介面獲取關於 JSX 的元素、屬性或子元素的型別資訊。它是一個黑盒。

JSX 函式返回型別

預設情況下,函式元件必須返回 JSX.Element | null。然而,這並不總是代表執行時行為。從 TypeScript 5.1 開始,你可以指定 JSX.ElementType 來覆蓋什麼是有效的 JSX 元件型別。請注意,這並不定義哪些 props 是有效的。Props 的型別始終由傳入元件的第一個引數定義。預設情況看起來大致如下

ts
namespace 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 = /*...*/;
}

嵌入表示式

JSX 允許你透過使用大括號 ({ }) 包圍表示式,在標籤之間嵌入表示式。

tsx
const a = (
<div>
{["foo", "bar"].map((i) => (
<span>{i / 2}</span>
))}
</div>
);

上面的程式碼會導致錯誤,因為你不能用字串除以數字。使用 preserve 選項時,輸出看起來如下

tsx
const a = (
<div>
{["foo", "bar"].map(function (i) {
return <span>{i / 2}</span>;
})}
</div>
);

React 整合

要在 React 中使用 JSX,你應該使用 React 型別定義。這些型別定義適當地為 React 定義了 JSX 名稱空間。

tsx
/// <reference path="react.d.ts" />
interface Props {
foo: string;
}
class MyComponent extends React.Component<Props, {}> {
render() {
return <span>{this.props.foo}</span>;
}
}
<MyComponent foo="bar" />; // ok
<MyComponent foo={0} />; // error

配置 JSX

有多個編譯器標誌可用於自定義 JSX,它們既可以作為編譯器標誌,也可以透過每檔案內聯的編譯指令 (pragmas) 使用。要了解更多資訊,請參閱它們的 tsconfig 參考頁面

TypeScript 文件是一個開源專案。透過 傳送 Pull Request 幫助我們改進這些頁面 ❤

此頁面的貢獻者
MHMohamed Hegazy (55)
OTOrta Therox (20)
RCRyan Cavanaugh (6)
DZDavid Zulaica (3)
KTKanchalai Tanglertsampan (3)
35+

最後更新:2026 年 3 月 27 日