# JSX ## 简介 [JSX](https://facebook.github.io/jsx/)是一种可嵌入式的、类似于XML的语法(JSX is an embeddable XML-like syntax)。其是为了被转换成有效的JavaScript,固然那种转换的语义是特定于具体实现的。JSX的流行,是由[React](https://reactjs.org/)框架的流行带来的,在其它应用中也有见到JSX。TypeScript支持JSX的嵌入、类型检查及将JSX直接编译到JavaScript。 ## 基本用法(Basic Usage) 为了使用JSX,必须完成两件事: 1. 以`.tsx`扩展名来命名文件 2. 开启`jsx`选项(编译器的) TypeScript本身带有三个JSX模式:`preserve`、`react`与`react-native`。这些模式仅影响生成阶段 -- 类型检查不受影响。`preserve`模式将保留JSX作为输出的部分,以被其它转换步骤(比如[Babel](https://babeljs.io/))进一步处理。此外该模式下的输出将有着`.jsx`文件扩展名。`react`模式将生成`React.createElement`,在使用钱不需要通过JSX转换,同时输出将有着`.js`文件扩展名。`react-native`模式与`preserve`等价,该模式下将保留所有JSX,但输出仍将为`.js`文件扩展名。 | 模式 | 输入 | 输出 | 输出文件扩展名 | | :--- | :--- | :--- | :--- | | `preserve` | `
` | `
` | `.jsx` | | `react` | `
` | `React.createElement("div")` | `.js` | | `react-native` | `
` | `
` | `.js` | 可通过命令行标志`--jsx`或在`tsconfig.json`文件中的相应选项,来对此模式进行指定。 > *注意:* 标识符`React`是硬编码的,因此必须要有`R`开头的React。 ## `as`运算符(The `as` Operator) 回顾一下类型断言(a type assertion)是怎么写的: ```typescript var foo = bar; ``` 这里对变量`bar`有着类型`foo`进行了断言。因为TypeScript也将尖括号用于类型断言,因此JSX的语法就引入了一些解析困难。结果就是,TypeScript不允许在`.tsx`文件中出现尖括号类型断言。 为了弥补`.tsx`文件中的这个功能损失,就加入了一个新的类型断言运算符:`as`。使用`as`运算符,上面的示例就可很容易写出来。 ```typescript var foo = bar as foo; ``` 在`.ts`与`.tsx`文件中,都可以使用`as`运算符,其作用与其它类型断言样式一致。 ## 类型检查(Type Checking) 为了理解JSX下的类型检查,就必须首先掌握固有元素与基于值的元素的区别(In order to understand type checking with JSX, you must first understand the difference between intrinsic elements and value-based elements)。对于一个JSX表达式``,`expr` 既可以是对环境中固有元素(比如DOM环境中的`div`或`span`)的参考,也可以是对某个创建出来的组件的参考。因为以下两个原因,这一点很重要: 1. 对于React, 固有元素生成的是字符串(`React.createElement("div")`),但对于创建出的组件则不是(`React.createElement(MyComponent)`)。 2. 传入给JSX的属性的类型,应以不同的方式进行查找。固有元素属性应本质上就知道,而组件将期望给它们指定一套属性(The types of the attributes being passed in the JSX element should be looked up differently. Intrinsic element attributes should be known *intrinsically* whereas components will likely want to specify their own set of attributes)。 对于如何区分二者,TypeScript使用了[与React相同的约定](http://facebook.github.io/react/docs/jsx-in-depth.html#html-tags-vs.-react-components)。固有元素总是以小写字母开头,而基于值的元素则全部以大写字母开头。 ### 固有元素(Intrinsic elements) 固有元素是在一个特殊接口`JSX.IntrinsicElements`上查找的。默认情况下,如果没有指定该接口,那么什么都可以且固有元素不会被检查类型。但如果指定了该接口,那么固有元素的名称将作为一个属性,在`JSX.IntrinsicElements`上进行查找。比如: ```typescript declare namespace JSX { interface IntrinsicElements { foo: any } } ; // 没有问题 ; // 错误 ``` 在上面的示例中,``将正确工作,但``将因为其没有在`JSX.IntrinsicElements`上进行指明,而导致一个错误。 > 注意: 也可以像下面这样,在`JSX.IntrinsicElements`上指定一个全能字符串索引器(a catch-all string indexer)。 ```typescript declare namespace JSX { interface IntrinsicElements { [elemName: string]: any; } } ``` ### 基于值的元素(Value-based elements) 基于值的元素,是简单地通过作用域中的标识符进行查找的。 ```typescript import MyComponent from "./MyComponent"; ; // 没有问题 ; // 错误 ``` 基于值元素的定义有两种方式: 1. 无状态函数式组件方式(Stateless Functional Component, SFC) 2. 类组件方式(Class Component) 因为在JSX表达式中二者难于区分,所以首先会尝试使用 **过载方案** 来将表达式作为无状态函数式组件进行解析(Because these two types of value-based elements are indistinguishable from each other in JSX expression, we first try to resolve the expression as Stateless Functional Component using **overload resolution**)。加入该过程成功,那么就完成了将表达式解析为其声明。而在将其解析为SFC失败时,就会尝试将其作为类组件进行解析。加入仍然失败,就会报告一个错误。 ***关于无状态函数式组件*** ***Stateless Functional Components*** 如同该名称所体现的那样,这种组件是以首个参数为`props`对象的JavaScript函数进行定义的。这里要强调,其定义函数的返回值,必须是可赋值给`JSX.Element`的类型 ```typescript interface FooProp { name: string; X: number; Y: number; } declare function AnotherComponent (prop: {name: string}); function ComponentFoo (prop: FooProp) { return ; } const Button = (prop: {value: string}, context: { color: string }) =>