TypeScript基礎入門之JSX(二)
屬性型別檢查
鍵入檢查屬性的第一步是確定元素屬性型別。 內在元素和基於價值的元素之間略有不同。
對於內部元素,它是JSX.IntrinsicElements上的屬性型別
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,則將使用類元素的建構函式或SFC呼叫的第一個引數的型別。
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中的屬性。 支援可選和必需的屬性。
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中的鍵。 進一步說,通用JSX.IntrinsicClassAttributes <T>型別也可用於為類元件(而不是SFC)指定相同型別的額外屬性。 在此型別中,泛型引數對應於類例項型別。 在React中,這用於允許型別為Ref <T>的ref屬性。 一般來說,這些介面上的所有屬性都應該是可選的,除非您打算讓JSX框架的使用者需要在每個標記上提供一些屬性。 擴充套件操作符也是有效的:
var props = { requiredProp: "bar" };
<foo {...props} />; // ok
var badProps = {};
<foo {...badProps} />; // error
子型別檢查
在TypeScript 2.3中,TS引入了子型別檢查。 children是元素屬性型別中的特殊屬性,其中子JSXExpressions被插入到屬性中。 類似於TS使用JSX.ElementAttributesProperty來確定props的名稱,TS使用JSX.ElementChildrenAttribute來確定這些props中的子項名稱。 應使用單個屬性宣告JSX.ElementChildrenAttribute。
declare namespace JSX {
interface ElementChildrenAttribute {
children: {}; // specify children name to use
}
}
<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 typings(如果您使用它們)。
interface PropsType {
children: JSX.Element
name: string
}
class Component extends React.Component<PropsType, {}> {
render() {
return (
<h2>
{this.props.children}
</h2>
)
}
}
// OK
<Component>
<h1>Hello World</h1>
</Component>
// Error: children is of type JSX.Element not array of JSX.Element
<Component>
<h1>Hello World</h1>
<h2>Hello World</h2>
</Component>
// Error: children is of type JSX.Element not array of JSX.Element or string.
<Component>
<h1>Hello</h1>
World
</Component>
JSX結果型別
預設情況下,JSX表示式的結果鍵入為any。您可以通過指定JSX.Element介面來自定義型別。但是,無法從此介面檢索有關JSX的元素,屬性或子級的型別資訊。這是一個黑盒子。
嵌入表示式
JSX允許您通過用大括號({})包圍表示式來在標記之間嵌入表示式。
var a = <div>
{["foo", "bar"].map(i => <span>{i / 2}</span>)}
</div>
上面的程式碼將導致錯誤,因為您不能將字串除以數字。 使用preserve選項時,輸出如下所示:
var a = <div>
{["foo", "bar"].map(function (i) { return <span>{i / 2}</span>; })}
</div>
React整合
要將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" />; // ok
<MyComponent foo={0} />; // error
工廠函式
jsx:react編譯器選項使用的確切工廠函式是可配置的。 可以使用jsxFactory命令列選項或內聯@jsx註釋編譯指示來設定它以基於每個檔案進行設定。 例如,如果將jsxFactory設定為createElement,則<div />將作為createElement("div")而不是React.createElement("div")來編譯。
註釋pragma版本可以像這樣使用(在TypeScript 2.8中):
import preact = require("preact");
/* @jsx preact.h */
const x = <div />;
編譯為
const preact = require("preact");
const x = preact.h("div", null);
選擇的工廠也將影響JSX名稱空間的查詢位置(用於型別檢查資訊),然後再回退到全域性名稱空間。 如果工廠定義為React.createElement(預設值),編譯器將在檢查全域性JSX之前檢查React.JSX。 如果工廠定義為h,它將在全域性JSX之前檢查h.JSX。