TypeScript JSX

2022-05-31 14:46 更新

JSX是一種嵌入式的類似XML的語法。 它可以被轉(zhuǎn)換成合法的JavaScript,盡管轉(zhuǎn)換的語義是依據(jù)不同的實(shí)現(xiàn)而定的。 JSX因 React框架而流行,但是也被其它應(yīng)用所使用。 TypeScript支持內(nèi)嵌,類型檢查和將JSX直接編譯為JavaScript。

基本用法

想要使用JSX必須做兩件事:

  1. 給文件一個(gè).tsx擴(kuò)展名
  2. 啟用jsx選項(xiàng)

TypeScript具有兩種JSX模式:preservereact。 這些模式只在代碼生成階段起作用 - 類型檢查并不受影響。 在 preserve模式下生成代碼中會(huì)保留JSX以供后續(xù)的轉(zhuǎn)換操作使用(比如:Babel)。 另外,輸出文件會(huì)帶有.jsx擴(kuò)展名。 react模式會(huì)生成React.createElement,在使用前不需要再進(jìn)行轉(zhuǎn)換操作了,輸出文件的擴(kuò)展名為.js

模式輸入輸出輸出文件擴(kuò)展名
preserve<div /><div />.jsx
react<div />React.createElement("div").js

你可以通過在命令行里使用--jsx標(biāo)記或tsconfig.json里的選項(xiàng)來指定模式。

注意:React標(biāo)識(shí)符是寫死的硬代碼,所以你必須保證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類型的。 因?yàn)門ypeScript也使用尖括號(hào)來表示類型斷言,JSX的語法帶來了解析的困難。因此,TypeScript在 .tsx文件里禁用了使用尖括號(hào)的類型斷言。

為了彌補(bǔ).tsx里的這個(gè)功能,新加入了一個(gè)類型斷言符號(hào):as。 上面的例子可以很容易地使用 as操作符改寫:

var foo = bar as foo;

as操作符在.ts.tsx里都可用,并且與其它類型斷言行為是等價(jià)的。

類型檢查

為了理解JSX的類型檢查,你必須首先理解固有元素與基于值的元素之間的區(qū)別。 假設(shè)有這樣一個(gè)JSX表達(dá)式 <expr />expr可能引用環(huán)境自帶的某些東西(比如,在DOM環(huán)境里的divspan)或者是你自定義的組件。 這是非常重要的,原因有如下兩點(diǎn):

  1. 對(duì)于React,固有元素會(huì)生成字符串(React.createElement("div")),然而由你自定義的組件卻不會(huì)生成(React.createElement(MyComponent))。
  2. 傳入JSX元素里的屬性類型的查找方式不同。 固有元素屬性 本身就支持,然而自定義的組件會(huì)自己去指定它們具有哪個(gè)屬性。

TypeScript使用與React相同的規(guī)范 來區(qū)別它們。 固有元素總是以一個(gè)小寫字母開頭,基于值的元素總是以一個(gè)大寫字母開頭。

固有元素

固有元素使用特殊的接口JSX.IntrinsicElements來查找。 默認(rèn)地,如果這個(gè)接口沒有指定,會(huì)全部通過,不對(duì)固有元素進(jìn)行類型檢查。 然而,如果接口存在,那么固有元素的名字需要在 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ò)誤

可以限制基于值的元素的類型。 然而,為了這么做我們需要引入兩個(gè)新的術(shù)語: 元素類的類型元素實(shí)例的類型

現(xiàn)在有<Expr />,元素類的類型Expr的類型。 所以在上面的例子里,如果 MyComponent是ES6的類,那么它的類類型就是這個(gè)類。 如果 MyComponent是個(gè)工廠函數(shù),類類型為這個(gè)函數(shù)。

一旦建立起了類類型,實(shí)例類型就確定了,為類類型調(diào)用簽名的返回值與構(gòu)造簽名的聯(lián)合類型。 再次說明,在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ò)誤

屬性類型檢查

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

對(duì)于固有元素,這是JSX.IntrinsicElements屬性的類型。

declare namespace JSX {
  interface IntrinsicElements {
    foo: { bar?: boolean }
  }
}

// `foo`的元素屬性類型為`{bar?: boolean}`
<foo bar />;

對(duì)于基于值的元素,就稍微復(fù)雜些。 它取決于先前確定的在元素實(shí)例類型上的某個(gè)屬性的類型。 至于該使用哪個(gè)屬性來確定類型取決于 JSX.ElementAttributesProperty。 它應(yīng)該使用單一的屬性來定義。 這個(gè)屬性名之后會(huì)被使用。

declare namespace JSX {
  interface ElementAttributesProperty {
    props; // 指定用來使用的屬性名
  }
}

class MyComponent {
  // 在元素實(shí)例類型上指定屬性
  props: {
    foo?: string;
  }
}

// `MyComponent`的元素屬性類型為`{foo?: string}`
<MyComponent foo="bar" />

元素屬性類型用于的JSX里進(jìn)行屬性的類型檢查。 支持可選屬性和必須屬性。

declare namespace JSX {
  interface IntrinsicElements {
    foo: { requiredProp: string; optionalProp?: number }
  }
}

<foo requiredProp="bar" />; // 正確
<foo requiredProp="bar" optionalProp={0} />; // 正確
<foo />; // 錯(cuò)誤, 缺少 requiredProp
<foo requiredProp={0} />; // 錯(cuò)誤, requiredProp 應(yīng)該是字符串
<foo requiredProp="bar" unknownProp />; // 錯(cuò)誤, unknownProp 不存在
<foo requiredProp="bar" some-unknown-prop />; // 正確, `some-unknown-prop`不是個(gè)合法的標(biāo)識(shí)符

注意:如果一個(gè)屬性名不是個(gè)合法的JS標(biāo)識(shí)符(像data-*屬性),并且它沒出現(xiàn)在元素屬性類型里時(shí)不會(huì)當(dāng)做一個(gè)錯(cuò)誤。

延展操作符也可以使用:

var props = { requiredProp: 'bar' };
<foo {...props} />; // 正確

var badProps = {};
<foo {...badProps} />; // 錯(cuò)誤

JSX結(jié)果類型

默認(rèn)地JSX表達(dá)式結(jié)果的類型為any。 你可以自定義這個(gè)類型,通過指定JSX.Element`接口。 然而,不能夠從接口里檢索元素,屬性或JSX的子元素的類型信息。 它是一個(gè)黑盒。

嵌入的表達(dá)式

JSX允許你使用{ }標(biāo)簽來內(nèi)嵌表達(dá)式。

var a = <div>
  {['foo', 'bar'].map(i => <span>{i / 2}</span>)}
</div>

上面的代碼產(chǎn)生一個(gè)錯(cuò)誤,因?yàn)槟悴荒苡脭?shù)字來除以一個(gè)字符串。 輸出如下,若你使用了 preserve選項(xiàng):

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

React整合

要想一起使用JSX和React,你應(yīng)該使用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} />; // 錯(cuò)誤


以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)