JSX是一種嵌入式的類似XML的語法。 它可以被轉(zhuǎn)換成合法的JavaScript,盡管轉(zhuǎn)換的語義是依據(jù)不同的實現(xiàn)而定的。 JSX因 React框架而流行,但是也被其它應用所使用。 TypeScript支持內(nèi)嵌,類型檢查和將JSX直接編譯為JavaScript。
想要使用JSX必須做兩件事:
.tsx
擴展名jsx
選項TypeScript具有兩種JSX模式:preserve
和react
。 這些模式只在代碼生成階段起作用 - 類型檢查并不受影響。 在 preserve
模式下生成代碼中會保留JSX以供后續(xù)的轉(zhuǎn)換操作使用(比如:Babel)。 另外,輸出文件會帶有.jsx
擴展名。 react
模式會生成React.createElement
,在使用前不需要再進行轉(zhuǎn)換操作了,輸出文件的擴展名為.js
。
模式 | 輸入 | 輸出 | 輸出文件擴展名 |
---|---|---|---|
preserve | <div /> | <div /> | .jsx |
react | <div /> | React.createElement("div") | .js |
你可以通過在命令行里使用--jsx
標記或tsconfig.json里的選項來指定模式。
注意:
React
標識符是寫死的硬代碼,所以你必須保證React(大寫的R)是可用的。 Note: The identifierReact
is hard-coded, so you must make React available with an uppercase R.
as
操作符回想一下怎么寫類型斷言:
var foo = <foo>bar;
這里我們斷言bar
變量是foo
類型的。 因為TypeScript也使用尖括號來表示類型斷言,JSX的語法帶來了解析的困難。因此,TypeScript在 .tsx
文件里禁用了使用尖括號的類型斷言。
為了彌補.tsx
里的這個功能,新加入了一個類型斷言符號:as
。 上面的例子可以很容易地使用 as
操作符改寫:
var foo = bar as foo;
as
操作符在.ts
和.tsx
里都可用,并且與其它類型斷言行為是等價的。
為了理解JSX的類型檢查,你必須首先理解固有元素與基于值的元素之間的區(qū)別。 假設有這樣一個JSX表達式 <expr />
,expr
可能引用環(huán)境自帶的某些東西(比如,在DOM環(huán)境里的div
或span
)或者是你自定義的組件。 這是非常重要的,原因有如下兩點:
React.createElement("div")
),然而由你自定義的組件卻不會生成(React.createElement(MyComponent)
)。TypeScript使用與React相同的規(guī)范 來區(qū)別它們。 固有元素總是以一個小寫字母開頭,基于值的元素總是以一個大寫字母開頭。
固有元素使用特殊的接口JSX.IntrinsicElements
來查找。 默認地,如果這個接口沒有指定,會全部通過,不對固有元素進行類型檢查。 然而,如果接口存在,那么固有元素的名字需要在 JSX.IntrinsicElements
接口的屬性里查找。 例如:
declare namespace JSX {
interface IntrinsicElements {
foo: any
}
}
<foo />; // 正確
<bar />; // 錯誤
在上例中,<foo />
沒有問題,但是<bar />
會報錯,因為它沒在JSX.IntrinsicElements
里指定。
注意:你也可以在
JSX.IntrinsicElements
上指定一個用來捕獲所有字符串索引:declare namespace JSX { interface IntrinsicElements { [elemName: string]: any; } }
基于值的元素會簡單的在它所在的作用域里按標識符查找。
import MyComponent from "./myComponent";
<MyComponent />; // 正確
<SomeOtherComponent />; // 錯誤
可以限制基于值的元素的類型。 然而,為了這么做我們需要引入兩個新的術語: 元素類的類型和元素實例的類型。
現(xiàn)在有<Expr />
,元素類的類型為Expr
的類型。 所以在上面的例子里,如果 MyComponent
是ES6的類,那么它的類類型就是這個類。 如果 MyComponent
是個工廠函數(shù),類類型為這個函數(shù)。
一旦建立起了類類型,實例類型就確定了,為類類型調(diào)用簽名的返回值與構(gòu)造簽名的聯(lián)合類型。 再次說明,在ES6類的情況下,實例類型為這個類的實例的類型,并且如果是工廠函數(shù),實例類型為這個函數(shù)返回值類型。
class MyComponent {
render() {}
}
// 使用構(gòu)造簽名
var myComponent = new MyComponent();
// 元素類的類型 => MyComponent
// 元素實例的類型 => { render: () => void }
function MyFactoryFunction() {
return {
render: () => {
}
}
}
// 使用調(diào)用簽名
var myComponent = MyFactoryFunction();
// 元素類的類型 => FactoryFunction
// 元素實例的類型 => { render: () => void }
元素的實例類型很有趣,因為它必須賦值給JSX.ElementClass
或拋出一個錯誤。 默認的 JSX.ElementClass
為{}
,但是它可以被擴展用來限制JSX的類型以符合相應的接口。
declare namespace JSX {
interface ElementClass {
render: any;
}
}
class MyComponent {
render() {}
}
function MyFactoryFunction() {
return { render: () => {} }
}
<MyComponent />; // 正確
<MyFactoryFunction />; // 正確
class NotAValidComponent {}
function NotAValidFactoryFunction() {
return {};
}
<NotAValidComponent />; // 錯誤
<NotAValidFactoryFunction />; // 錯誤
屬性類型檢查的第一步是確定元素屬性類型。 這在固有元素和基于值的元素之間稍有不同。
對于固有元素,這是JSX.IntrinsicElements
屬性的類型。
declare namespace JSX {
interface IntrinsicElements {
foo: { bar?: boolean }
}
}
// `foo`的元素屬性類型為`{bar?: boolean}`
<foo bar />;
對于基于值的元素,就稍微復雜些。 它取決于先前確定的在元素實例類型上的某個屬性的類型。 至于該使用哪個屬性來確定類型取決于 JSX.ElementAttributesProperty
。 它應該使用單一的屬性來定義。 這個屬性名之后會被使用。
declare namespace JSX {
interface ElementAttributesProperty {
props; // 指定用來使用的屬性名
}
}
class MyComponent {
// 在元素實例類型上指定屬性
props: {
foo?: string;
}
}
// `MyComponent`的元素屬性類型為`{foo?: string}`
<MyComponent foo="bar" />
元素屬性類型用于的JSX里進行屬性的類型檢查。 支持可選屬性和必須屬性。
declare namespace JSX {
interface IntrinsicElements {
foo: { requiredProp: string; optionalProp?: number }
}
}
<foo requiredProp="bar" />; // 正確
<foo requiredProp="bar" optionalProp={0} />; // 正確
<foo />; // 錯誤, 缺少 requiredProp
<foo requiredProp={0} />; // 錯誤, requiredProp 應該是字符串
<foo requiredProp="bar" unknownProp />; // 錯誤, unknownProp 不存在
<foo requiredProp="bar" some-unknown-prop />; // 正確, `some-unknown-prop`不是個合法的標識符
注意:如果一個屬性名不是個合法的JS標識符(像
data-*
屬性),并且它沒出現(xiàn)在元素屬性類型里時不會當做一個錯誤。
延展操作符也可以使用:
var props = { requiredProp: 'bar' };
<foo {...props} />; // 正確
var badProps = {};
<foo {...badProps} />; // 錯誤
默認地JSX表達式結(jié)果的類型為any。 你可以自定義這個類型,通過指定
JSX.Element`接口。 然而,不能夠從接口里檢索元素,屬性或JSX的子元素的類型信息。 它是一個黑盒。
JSX允許你使用{ }
標簽來內(nèi)嵌表達式。
var a = <div>
{['foo', 'bar'].map(i => <span>{i / 2}</span>)}
</div>
上面的代碼產(chǎn)生一個錯誤,因為你不能用數(shù)字來除以一個字符串。 輸出如下,若你使用了 preserve
選項:
var a = <div>
{['foo', 'bar'].map(function (i) { return <span>{i / 2}</span>; })}
</div>
要想一起使用JSX和React,你應該使用React類型定義。 這些類型聲明定義了 JSX
合適命名空間來使用React。
/// <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" />; // 正確
<MyComponent foo={0} />; // 錯誤
更多建議: