JSX

一种嵌入式的类似XML的语法。 它可以被转换成合法的JavaScript。常见的有React,博客所用到的库为inferno也是jsx。

JSX模式

|模式| 输入| 输出| 输出文件扩展名|
|preserve| <div />| <div /> |.jsx|
|react| <div />| React.createElement(“div”)| .js|
|react-native| <div />| <div /> |.js|

as 操作符

1
var foo = <foo>bar;

上述代码将bar变量断言为foo类型。但是在jsx语法中解析会很困难。因此jsx禁用了尖括号的写法。ts通过提供 as 操作符来让jsx语法更好的实现断言。as操作符在ts和tsx文件都可以使用,与尖括号的行为是等价的

类型检查

固有元素与基于值的元素之间的区别

为什么要区分固有元素与基于值的元素

  1. 对于React,固有元素会生成字符串(React.createElement(“div”)),然而由你自定义的组件却不会生成(React.createElement(MyComponent))
  2. 传入JSX元素里的属性类型的查找方式不同

固有元素总是以一个小写字母开头,基于值的元素总是以一个大写字母开头

固有元素

如果指定了接口JSX.IntrinsicElements,则通过接口查找,如果接口没有指定则全部通过,不对固有元素进行检查。

1
2
3
4
5
6
7
8
declare namespace JSX {
interface IntrinsicElements {
foo: any
}
}

<foo />; // 正确
<bar />; // 错误

基于值的元素

基于值的元素会简单的在它所在的作用域里按标识符查找

1
2
3
4
import MyComponent from "./myComponent";

<MyComponent />; // 正确
<SomeOtherComponent />; // 错误

有两种方式可以定义基于值的元素:

  1. 函数组件 (FC)
  2. 类组件

由于这两种基于值的元素在JSX表达式里无法区分,因此TypeScript首先会尝试将表达式做为函数组件进行解析。如果解析成功,那么TypeScript就完成了表达式到其声明的解析操作。否则尝试以类组件进行解析,如果依旧失败,则抛出错误

函数组件
TypeScript会强制函数组件的返回值可以赋值给JSX.Element,由于函数组件是简单的JavaScript函数,所以我们还可以利用函数重载。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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 {
...
}

类组件

类组件的类型允许自定义。但是需要分清两个术语:元素类的类型 元素实例的类型

对于 class 来说,类类型是类的构造函数和静态部分。实例类型是class的实例的类型
对于工厂函数来说,类类型为函数,实例类型为函数的返回值类型

元素的实例类型必须赋值给JSX.ElementClass或抛出一个错误。默认的JSX.ElementClass{},但可以被扩展用来限制JSX的类型以符合相应的接口

属性类型检查

属性类型检查的第一步是确定_元素属性类型_。 这在固有元素和基于值的元素之间稍有不同

对于固有元素,是JSX.IntrinsicElements属性的类型

1
2
3
4
5
6
7
8
declare namespace JSX {
interface IntrinsicElements {
foo: { bar?: boolean }
}
}

// `foo`的元素属性类型为`{bar?: boolean}`
<foo bar />;

对于基于值的元素,取决于先前确定的在元素实例类型上的某个属性的类型。至于该使用哪个属性来确定类型取决于JSX.ElementAttributesProperty
它应该使用单一的属性来定义。 这个属性名之后会被使用
TypeScript 2.8,如果未指定JSX.ElementAttributesProperty,那么将使用类元素构造函数或函数组件调用的第一个参数的类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
declare namespace JSX {
interface ElementAttributesProperty {
props; // 指定用来使用的属性名
}
}

class MyComponent {
// 在元素实例类型上指定属性
props: {
foo?: string;
}
}

// `MyComponent`的元素属性类型为`{foo?: string}`
<MyComponent foo="bar" />

属性的检查支持支持可选属性和必须属性

JSX还会使用JSX.IntrinsicAttributes接口来指定额外的属性,这些额外的属性通常不会被组件的props或arguments使用,例如React里的key。

JSX.IntrinsicClassAttributes泛型类型也可以用来为类组件(非函数组件)指定相同种类的额外属性。泛型参数表示类实例类型。在React里,它用来允许Ref类型上的ref属性

子孙类型检查

TypeScript 2.3 引入了children类型检查。利用JSX.ElementChildrenAttribute来决定children名。JSX.ElementChildrenAttribute应该被声明在单一的属性(property)里。

1
2
3
4
5
declare namespace JSX {
interface ElementChildrenAttribute {
children: {}; // specify children name to use
}
}

如不特殊指定子孙的类型,我们将使用React typings 里的默认类型

JSX结果类型

默认地JSX表达式结果的类型为any,可以自定义这个类型,通过指定JSX.Element接口

不能够从接口里检索元素,属性或JSX的子元素的类型信息。 它是一个黑盒

作者

徐云飞

发布于

2022-10-29

更新于

2023-02-05

许可协议

评论