非常教程

Typescript参考手册

JSX

JSX

介绍

JSX是一种可嵌入XML的语法。它意味着被转换成有效的JavaScript,尽管该转换的语义是特定于实现的。JSX开始受到React框架的欢迎,但之后也出现了其他应用程序。TypeScript支持嵌入,类型检查和JSX直接编译成JavaScript。

基本用法

为了使用JSX,你必须做两件事。

  1. .tsx扩展名命名您的文件
  1. 启用jsx选项

TypeScript有三个JSX模式:preservereact,和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可以引用环境内在的东西(例如,一个divspan一个DOM环境)或者一个你创建的自定义组件。这很重要,原因有两个:

  1. 对于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

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

  1. 无状态功能组件(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
JSX