JSX

JSX 是一種可嵌入的類似 XML 的語法。它用於轉換成有效的 JavaScript,儘管轉換的語意取決於實作。JSX 因 React 框架而廣受歡迎,但後來也出現了其他實作。TypeScript 支援將 JSX 直接嵌入、檢查類型和編譯成 JavaScript。

基本用法

要使用 JSX,您必須執行兩件事。

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

TypeScript 附帶三種 JSX 模式:preservereactreact-native。這些模式只會影響發射階段,不會影響類型檢查。preserve 模式會將 JSX 保留為輸出的部分,供其他轉換步驟(例如 Babel)進一步使用。此外,輸出將有 .jsx 檔案副檔名。react 模式會發射 React.createElement,使用前不需要經過 JSX 轉換,而輸出將有 .js 檔案副檔名。react-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 命令列旗標或對應的選項 jsx in your tsconfig.json 檔案來指定此模式。

*注意:使用 jsxFactory 選項(預設為 React.createElement)可以指定在針對 react JSX 發射時要使用的 JSX 工廠函式。

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 的類型檢查,您必須先瞭解內建元素和基於值的元素之間的差異。給定 JSX 表達式 <expr />expr 可能參照環境中的內建元素(例如 DOM 環境中的 divspan)或您建立的客製化元件。這很重要,原因有二

  1. 對於 React,內建元素會以字串發射(React.createElement("div")),而您建立的元件則不會(React.createElement(MyComponent))。
  2. 在 JSX 元素中傳遞的屬性類型應該以不同的方式查詢。內建元素屬性應該內建已知,而元件可能會想要指定自己的屬性集。

TypeScript 使用 與 React 相同的慣例 來區分這些。內建元素總是從小寫字母開始,而基於值的元素總是從大寫字母開始。

內建元素

內建元素會在特殊介面 JSX.IntrinsicElements 上查詢。預設情況下,如果未指定此介面,則任何內容都不會受到類型檢查。但是,如果存在此介面,則會將內建元素的名稱作為 JSX.IntrinsicElements 介面上的屬性進行查詢。例如

ts
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;
}
}

基於值的元素

基於值的元素僅透過範圍內的識別碼查詢。

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

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

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

由於這兩種基於值的元素類型在 JSX 表達式中無法區分,因此 TS 會先嘗試使用重載解析來將表達式解析為函式元件。如果處理程序成功,則 TS 會完成將表達式解析為其宣告。如果無法將值解析為函式元件,TS 會嘗試將其解析為類別元件。如果失敗,TS 會回報錯誤。

函式元件

如同名稱所示,元件定義為 JavaScript 函式,其第一個引數是 props 物件。TS 強制執行其傳回類型必須可指派給 JSX.Element

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

類別元件

可以定義類別元件的類型。不過,為此最好先了解兩個新名詞:元素類別類型元素實例類型

針對 <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 的使用,僅限於符合適當介面的類型。

ts
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 上的屬性類型

ts
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,則會改用類別元素建構函式或函式元件呼叫的第一個參數的類型。

ts
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 中的屬性。支援選擇性和必要屬性。

ts
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 屬性為 Ref<T> 類型。一般來說,這些介面上的所有屬性都應該是選用的,除非您打算讓 JSX 架構的使用者需要在每個標籤上提供一些屬性。

擴散運算子也適用

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

子項類型檢查

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

ts
declare namespace JSX {
interface ElementChildrenAttribute {
children: {}; // specify children name to use
}
}
ts
<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>

您可以指定 子項 的類型,就像其他屬性一樣。如果您使用它們,這將覆寫預設類型,例如 React 編寫 中的類型。

ts
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 允許你在標籤之間內嵌表達式,方法是使用大括號 ({ }) 將表達式括起來。

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

由於無法將字串除以數字,因此以上的程式碼會產生錯誤。當使用 preserve 選項時,輸出看起來像

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

React 整合

若要將 JSX 與 React 搭配使用,您應該使用 React 打字。這些打字會適當地定義 JSX 名稱空間,以供與 React 搭配使用。

ts
/// <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,它們同時作為編譯器旗標和透過內嵌每個檔案實用指令運作。若要深入了解,請參閱其 tsconfig 參考頁面

TypeScript 文件是一個開放原始碼專案。透過 傳送 Pull Request,協助我們改善這些頁面 ❤

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

最後更新時間:2024 年 3 月 21 日