轉發 TypeScript基礎入門之JSX(一)react
JSX是一種可嵌入的相似XML的語法。 它旨在轉換爲有效的JavaScript,儘管該轉換的語義是特定於實現的。 JSX在React框架中愈來愈受歡迎,但此後也看到了其餘實現。 TypeScript支持嵌入,類型檢查和直接編譯JSX到JavaScript。json
要使用JSX,您必須作兩件事。 1. 使用.tsx擴展名命名您的文件 2. 啓用jsx選項react-native
TypeScript附帶三種JSX模式:preserve, react 和 react-native。 這些模式僅影響編譯階段 - 類型檢查不受影響。 preserve模式將保持JSX做爲輸出的一部分,以便由另外一個變換步驟(例如Babel)進一步編譯。 此外,輸出將具備.jsx文件擴展名。 react模式將編譯React.createElement,在使用以前不須要通過JSX轉換,輸出將具備.js文件擴展名。 react-native模式至關於保留,由於它保留了全部JSX,但輸出將具備.js文件擴展名。框架
Mode | Input | Output | Output File Extension |
---|---|---|---|
preserve | .jsx | ||
react | React.createElement(「div」) | .js | |
react-native | .js |
您可使用–jsx命令行標誌或tsconfig.json文件中的相應選項指定此模式。ide
注意:標識符React是硬編碼的,所以必須使用大寫的R使React可用函數
回想一下如何寫一個類型斷言:編碼
var foo = <foo>bar;
聲明變量bar的類型爲foo。 因爲TypeScript還對類型斷言使用尖括號,所以將其與JSX的語法結合會引入某些解析困難。 所以,TypeScript不容許.tsx文件中的尖括號類型斷言。spa
因爲上述語法不能在.tsx文件中使用,所以應使用備用類型斷言運算符:as。 可使用as運算符輕鬆重寫該示例。命令行
var foo = bar as foo;
as運算符在.ts和.tsx文件中都可用,而且行爲與尖括號類型斷言樣式相同。code
爲了理解使用JSX進行類型檢查,您必須首先了解內部元素和基於值的元素之間的區別。 給定JSX表達式,expr能夠引用環境固有的東西(例如DOM環境中的div或span)或者您建立的自定義組件。 這有兩個重要緣由:
TypeScript使用與React相同的約定來區分它們。內部元素始終以小寫字母開頭,而基於值的元素始終以大寫字母開頭。
內在元素 在特殊接口JSX.IntrinsicElements上查找內部元素。 默認狀況下,若是未指定此接口,則會執行任何操做,而且不會對內部元素進行類型檢查。 可是,若是存在此接口,則將內部元素的名稱做爲JSX.IntrinsicElements接口上的屬性進行查找。 例如:
declare namespace JSX { interface IntrinsicElements { foo: any } } <foo />; // ok <bar />; // error
在上面的示例中,將正常工做,但將致使錯誤,由於它還沒有在JSX.IntrinsicElements上指定。
注意:您還能夠在JSX.IntrinsicElements上指定catch-all字符串索引器,以下所示:
declare namespace JSX { interface IntrinsicElements { [elemName: string]: any; } }
基於值的要素
基於值的元素只需經過範圍內的標識符進行查找。
import MyComponent from "./myComponent"; <MyComponent />; // ok <SomeOtherComponent />; // error
有兩種方法能夠定義基於值的元素:
由於這兩種類型的基於值的元素在JSX表達式中沒法區分,因此首先TS嘗試使用重載解析將表達式解析爲無狀態功能組件。 若是該過程成功,則TS完成將表達式解析爲其聲明。 若是該值沒法解析爲SFC,則TS將嘗試將其解析爲類組件。 若是失敗,TS將報告錯誤。
無狀態功能組件
顧名思義,該組件被定義爲JavaScript函數,其第一個參數是props對象。 TS強制其返回類型必須可分配給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>
由於SFC只是一個JavaScript函數,因此這裏也可使用函數重載:
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 { ... }
類組件
能夠定義類組件的類型。 可是,要這樣作,最好理解兩個新術語:元素類類型和元素實例類型。
給定,元素類類型是Expr的類型。 所以,在上面的示例中,若是MyComponent是ES6類,則類類型將是該類的構造函數和靜態。 若是MyComponent是工廠函數,則類類型將是該函數。
一旦創建了類類型,實例類型就由類類型構造的返回類型或調用簽名(不管哪一個存在)的並集決定。 一樣,在ES6類的狀況下,實例類型將是該類的實例的類型,而且在工廠函數的狀況下,它將是從函數返回的值的類型。
class MyComponent { render() {} } // use a construct signature var myComponent = new MyComponent(); // element class type => MyComponent // element instance type => { render: () => void } function MyFactoryFunction() { return { render: () => { } } } // use a call signature var myComponent = MyFactoryFunction(); // element class type => FactoryFunction // element instance type => { render: () => void }
元素實例類型頗有趣,由於它必須能夠賦值給JSX.ElementClass,不然會致使錯誤。 默認狀況下,JSX.ElementClass是{},但能夠對其進行擴充,以將JSX的使用僅限於那些符合正確接口的類型。
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