介紹
JSX是一種嵌入式的類似XML的語法慧耍。 它可以被轉(zhuǎn)換成合法的JavaScript,盡管轉(zhuǎn)換的語義是依據(jù)不同的實(shí)現(xiàn)而定的滚躯。 JSX因React框架而流行胃惜,但也存在其它的實(shí)現(xiàn)。 TypeScript支持內(nèi)嵌鼎姐,類型檢查以及將JSX直接編譯為JavaScript钾麸。
基本用法
想要使用JSX必須做兩件事:
給文件一個(gè).tsx擴(kuò)展名
啟用jsx選項(xiàng)
TypeScript具有三種JSX模式:preserve,react和react-native炕桨。 這些模式只在代碼生成階段起作用 - 類型檢查并不受影響饭尝。 在preserve模式下生成代碼中會(huì)保留JSX以供后續(xù)的轉(zhuǎn)換操作使用(比如:Babel)。 另外献宫,輸出文件會(huì)帶有.jsx擴(kuò)展名钥平。 react模式會(huì)生成React.createElement,在使用前不需要再進(jìn)行轉(zhuǎn)換操作了姊途,輸出文件的擴(kuò)展名為.js涉瘾。 react-native相當(dāng)于preserve,它也保留了所有的JSX捷兰,但是輸出文件的擴(kuò)展名是.js立叛。
模式 輸入 輸出 輸出文件擴(kuò)展名
preserve <div /> <div /> .jsx
react <div /> React.createElement("div") .js
react-native <div /> <div /> .js
你可以通過在命令行里使用--jsx標(biāo)記或tsconfig.json里的選項(xiàng)來指定模式。
注意:React標(biāo)識(shí)符是寫死的硬編碼贡茅,所以你必須保證React(大寫的R)是可用的秘蛇。
as操作符
回想一下怎么寫類型斷言:
var foo = <foo>bar;
這里斷言bar變量是foo類型的。 因?yàn)門ypeScript也使用尖括號(hào)來表示類型斷言友扰,在結(jié)合JSX的語法后將帶來解析上的困難彤叉。因此,TypeScript在.tsx文件里禁用了使用尖括號(hào)的類型斷言村怪。
由于不能夠在.tsx文件里使用上述語法秽浇,因此我們應(yīng)該使用另一個(gè)類型斷言操作符:as。 上面的例子可以很容易地使用as操作符改寫:
var foo = bar as foo;
as操作符在.ts和.tsx里都可用甚负,并且與尖括號(hào)類型斷言行為是等價(jià)的柬焕。
類型檢查
為了理解JSX的類型檢查,你必須首先理解固有元素與基于值的元素之間的區(qū)別梭域。 假設(shè)有這樣一個(gè)JSX表達(dá)式<expr />斑举,expr可能引用環(huán)境自帶的某些東西(比如,在DOM環(huán)境里的div或span)或者是你自定義的組件病涨。 這是非常重要的富玷,原因有如下兩點(diǎn):
對(duì)于React,固有元素會(huì)生成字符串(React.createElement("div")),然而由你自定義的組件卻不會(huì)生成(React.createElement(MyComponent))赎懦。
傳入JSX元素里的屬性類型的查找方式不同雀鹃。 固有元素屬性本身就支持,然而自定義的組件會(huì)自己去指定它們具有哪個(gè)屬性励两。
TypeScript使用與React相同的規(guī)范 來區(qū)別它們黎茎。 固有元素總是以一個(gè)小寫字母開頭,基于值的元素總是以一個(gè)大寫字母開頭当悔。
固有元素
固有元素使用特殊的接口JSX.IntrinsicElements來查找傅瞻。 默認(rèn)地,如果這個(gè)接口沒有指定盲憎,會(huì)全部通過嗅骄,不對(duì)固有元素進(jìn)行類型檢查。 然而焙畔,如果這個(gè)接口存在掸读,那么固有元素的名字需要在JSX.IntrinsicElements接口的屬性里查找。 例如:
declare namespace JSX {
? ? interface IntrinsicElements {
? ? ? ? foo: any
? ? }
}
<foo />; // 正確
<bar />; // 錯(cuò)誤
在上例中宏多,<foo />沒有問題儿惫,但是<bar />會(huì)報(bào)錯(cuò),因?yàn)樗鼪]在JSX.IntrinsicElements里指定伸但。
注意:你也可以在JSX.IntrinsicElements上指定一個(gè)用來捕獲所有字符串索引:
declare namespace JSX {
? ? interface IntrinsicElements {
? ? ? ? [elemName: string]: any;
? ? }
}
基于值的元素
基于值的元素會(huì)簡(jiǎn)單的在它所在的作用域里按標(biāo)識(shí)符查找肾请。
import MyComponent from "./myComponent";
<MyComponent />; // 正確
<SomeOtherComponent />; // 錯(cuò)誤
有兩種方式可以定義基于值的元素:
無狀態(tài)函數(shù)組件 (SFC)
類組件
由于這兩種基于值的元素在JSX表達(dá)式里無法區(qū)分,因此TypeScript首先會(huì)嘗試將表達(dá)式做為無狀態(tài)函數(shù)組件進(jìn)行解析更胖。如果解析成功铛铁,那么TypeScript就完成了表達(dá)式到其聲明的解析操作。如果按照無狀態(tài)函數(shù)組件解析失敗却妨,那么TypeScript會(huì)繼續(xù)嘗試以類組件的形式進(jìn)行解析饵逐。如果依舊失敗,那么將輸出一個(gè)錯(cuò)誤彪标。
無狀態(tài)函數(shù)組件
正如其名倍权,組件被定義成JavaScript函數(shù),它的第一個(gè)參數(shù)是props對(duì)象捞烟。 TypeScript會(huì)強(qiáng)制它的返回值可以賦值給JSX.Element薄声。
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>
由于無狀態(tài)函數(shù)組件是簡(jiǎn)單的JavaScript函數(shù),所以我們還可以利用函數(shù)重載题画。
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 {
? ? ...
}
類組件
我們可以定義類組件的類型默辨。 然而,我們首先最好弄懂兩個(gè)新的術(shù)語:元素類的類型和元素實(shí)例的類型苍息。
現(xiàn)在有<Expr />缩幸,元素類的類型為Expr的類型壹置。 所以在上面的例子里,如果MyComponent是ES6的類表谊,那么類類型就是類的構(gòu)造函數(shù)和靜態(tài)部分蒸绩。 如果MyComponent是個(gè)工廠函數(shù),類類型為這個(gè)函數(shù)铃肯。
一旦建立起了類類型,實(shí)例類型由類構(gòu)造器或調(diào)用簽名(如果存在的話)的返回值的聯(lián)合構(gòu)成传蹈。 再次說明押逼,在ES6類的情況下,實(shí)例類型為這個(gè)類的實(shí)例的類型惦界,并且如果是工廠函數(shù)挑格,實(shí)例類型為這個(gè)函數(shù)返回值類型。
class MyComponent {
? ? render() {}
}
// 使用構(gòu)造簽名
var myComponent = new MyComponent();
// 元素類的類型 => MyComponent
// 元素實(shí)例的類型 => { render: () => void }
function MyFactoryFunction() {
? ? return {
? ? render: () => {
? ? }
? ? }
}
// 使用調(diào)用簽名
var myComponent = MyFactoryFunction();
// 元素類的類型 => FactoryFunction
// 元素實(shí)例的類型 => { render: () => void }
元素的實(shí)例類型很有趣沾歪,因?yàn)樗仨氋x值給JSX.ElementClass或拋出一個(gè)錯(cuò)誤漂彤。 默認(rèn)的JSX.ElementClass為{},但是它可以被擴(kuò)展用來限制JSX的類型以符合相應(yīng)的接口灾搏。
declare namespace JSX {
? ? interface ElementClass {
? ? render: any;
? ? }
}
class MyComponent {
? ? render() {}
}
function MyFactoryFunction() {
? ? return { render: () => {} }
}
<MyComponent />; // 正確
<MyFactoryFunction />; // 正確
class NotAValidComponent {}
function NotAValidFactoryFunction() {
? ? return {};
}
<NotAValidComponent />; // 錯(cuò)誤
<NotAValidFactoryFunction />; // 錯(cuò)誤