JSX
JSX
介绍
JSX是一种可嵌入XML的语法。它意味着被转换成有效的JavaScript,尽管该转换的语义是特定于实现的。JSX开始受到React框架的欢迎,但之后也出现了其他应用程序。TypeScript支持嵌入,类型检查和JSX直接编译成JavaScript。
基本用法
为了使用JSX,你必须做两件事。
- 用
.tsx
扩展名命名您的文件
- 启用
jsx
选项
TypeScript有三个JSX模式:preserve
,react
,和react-native
。这些模式仅影响发射阶段 - 类型检查不受影响。该preserve
模式将JSX保留为输出的一部分,以供其他转换步骤(例如Babel)进一步使用。此外,输出将具有.jsx
文件扩展名。该react
模式将发出React.createElement
,在使用之前不需要经过JSX转换,并且输出将具有.js
文件扩展名。该react-native
模式相当于preserve
它保留了所有JSX,但输出将具有.js
文件扩展名。
模式 | 输入 | 产量 | 输出文件扩展名 |
---|---|---|---|
preserve | <div /> | <div /> | .jsx |
react | <div /> | React.createElement( “分区”) | JS |
react-native | <div /> | <div /> | JS |
您可以使用--jsx
命令行标志或tsconfig.json文件中的相应选项指定此模式。
注意:标识符
是硬编码的,所以您必须使React以大写R.React
as操作符
回想一下如何编写一个类型断言:
var foo = <foo>bar;
在这里,我们声明变量bar
具有类型foo
。由于TypeScript对类型断言也使用尖括号,所以JSX的语法引入了一些解析困难。因此,TypeScript不允许在.tsx
文件中使用尖括号类型断言。
为弥补这种.tsx
文件功能的损失,增加了一个新的断言操作符:as
。上面的例子很容易被as
操作员重写。
var foo = bar as foo;
该as
运营商在这两个可用的.ts
和.tsx
文件,并在行为上的其他类型的断言风格相同。
类型检查
为了理解JSX的类型检查,您必须首先了解内部元素和基于值的元素之间的区别。给定一个JSX表达式<expr />,expr可以引用环境内在的东西(例如,一个div或span一个DOM环境)或者一个你创建的自定义组件。这很重要,原因有两个:
- 对于React,内部元素以字符串(
React.createElement("div")
)形式发出,而您创建的组件不是(React.createElement(MyComponent)
)。
2. 在JSX元素中传递的属性类型应该以不同的方式查找。内在元素属性应该是本质
上已知的,而组件可能想要指定它们自己的一组属性。
TypeScript使用与React相同的约定来区分这些约定。内部元素始终以小写字母开头,而基于值的元素始终以大写字母开头。
内在因素
内部元素在特殊界面上查找JSX.IntrinsicElements
。默认情况下,如果未指定此接口,则会发生任何情况,并且不会对内部元素进行类型检查。但是
,如果该接口是
现在,则内在元素的名称抬头的一个属性JSX.IntrinsicElements
界面。例如:
declare namespace JSX {
interface IntrinsicElements {
foo: any
}
}
<foo />; // ok
<bar />; // error
在上面的例子中,<foo />将工作正常,但<bar />会导致一个错误,因为它没有被指定JSX.IntrinsicElements。
注意:你也可以指定一个catch-all字符串索引器,
JSX.IntrinsicElements
如下所示:declare namespace JSX {interface IntrinsicElements {elemName:string:any; }}
基于价值的元素
基于价值的元素只是由范围内的标识符查找。
import MyComponent from "./myComponent";
<MyComponent />; // ok
<SomeOtherComponent />; // error
有两种方法可以定义基于值的元素:
- 无状态功能组件(SFC)
2. 类组件
由于这两种基于值的元素在JSX表达式中不可区分,我们首先尝试使用重载解析将表达式解析为无状态功能组件。如果这个过程成功了,那么我们就完成了对其声明的表达。如果我们不能作为SFC来解决,我们将尝试作为类组件解决。如果失败了,我们会报告一个错误。
无状态功能组件
顾名思义,该组件被定义为JavaScript函数,其第一个参数是一个props
对象。我们强制它的返回类型必须是可赋值的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 />,元素类的类型是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
属性类型检查
键入检查属性的第一步是确定元素属性类型
。这与内在要素和基于价值的要素略有不同。
对于内在元素,它是属性的类型 JSX.IntrinsicElements
declare namespace JSX {
interface IntrinsicElements {
foo: { bar?: boolean }
}
}
// element attributes type for 'foo' is '{bar?: boolean}'
<foo bar />;
对于基于价值的元素,它有点复杂。它由之前确定的元素实例类型
的属性类型
确定。决定使用哪个属性JSX.ElementAttributesProperty
。它应该用一个属性来声明。然后使用该属性的名称。
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-*
属性),如果在元素属性类型中找不到它,则不会将其视为错误。
传播运算符也可以工作:
var props = { requiredProp: "bar" };
<foo {...props} />; // ok
var badProps = {};
<foo {...badProps} />; // error
子元素类型检查
在2.3中,介绍了子元素的
类型检查。子元素
是元素属性类型
中的一个属性
,我们通过类型检查属性
确定它。与我们如何JSX.ElementAttributesProperty
确定道具
的名称类似,我们用它JSX.ElementChildrenAttribute
来确定子元素的
名字。JSX.ElementChildrenAttribute
应该用一个属性
声明。
declare namespace JSX {
interface ElementChildrenAttribute {
children: {}; // specify children name to use
}
}
如果没有明确指定子类型,我们将使用React类型中的默认类型。
<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类型的默认类型。
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>
反应整合
要将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