当前位置:网站首页>LayaBox---TypeScript---JSX
LayaBox---TypeScript---JSX
2022-08-02 10:01:00 【格拉格拉】
目录
TypeScript具有三种JSX模式:preserve,react和react-native。
1.介绍
JSX是一种嵌入式的类似XML的语法。 它可以被转换成合法的JavaScript,尽管转换的语义是依据不同的实现而定的。 JSX因React框架而流行,但也存在其它的实现。 TypeScript支持内嵌,类型检查以及将JSX直接编译为JavaScript。
2.基本语法
想要使用JSX必须做两件事:
- 给文件一个
.tsx扩展名- 启用
jsx选项
TypeScript具有三种JSX模式:preserve,react和react-native。
这些模式只在代码生成阶段起作用 - 类型检查并不受影响。 在preserve模式下生成代码中会保留JSX以供后续的转换操作使用(比如:Babel)。 另外,输出文件会带有.jsx扩展名。 react模式会生成React.createElement,在使用前不需要再进行转换操作了,输出文件的扩展名为.js。 react-native相当于preserve,它也保留了所有的JSX,但是输出文件的扩展名是.js。
| 模式 | 输入 | 输出 | 输出文件扩展名 |
| preserve | <div /> | <div /> | .jsx |
| react | <div /> | React.creatElement("div") | .js |
| react-native | <div /> | <div /> | .js |
你可以通过在命令行里使用--jsx标记或tsconfig.json里的选项来指定模式。
注意:
React标识符是写死的硬编码,所以你必须保证React(大写的R)是可用的。
3.as操作符
var foo = <foo>bar;这里断言bar变量是foo类型的。 因为TypeScript也使用尖括号来表示类型断言,在结合JSX的语法后将带来解析上的困难。因此,TypeScript在.tsx文件里禁用了使用尖括号的类型断言。
由于不能够在.tsx文件里使用上述语法,因此我们应该使用另一个类型断言操作符:as。 上面的例子可以很容易地使用as操作符改写:
var foo = bar as foo;as操作符在.ts和.tsx里都可用,并且与尖括号类型断言行为是等价的。
4.类型检查
为了理解JSX的类型检查,你必须首先理解固有元素与基于值的元素之间的区别。
假设有这样一个JSX表达式
<expr />,expr可能引用环境自带的某些东西(比如,在DOM环境里的div或span)或者是你自定义的组件。 这是非常重要的,原因有如下两点:
- 对于React,固有元素会生成字符串(
React.createElement("div")),然而由你自定义的组件却不会生成(React.createElement(MyComponent))。- 传入JSX元素里的属性类型的查找方式不同。 固有元素属性本身就支持,然而自定义的组件会自己去指定它们具有哪个属性。
TypeScript使用与React相同的规范 来区别它们。 固有元素总是以一个小写字母开头,基于值的元素总是以一个大写字母开头。
4.1固有元素
固有元素使用特殊的接口JSX.IntrinsicElements来查找。 默认地,如果这个接口没有指定,会全部通过,不对固有元素进行类型检查。 然而,如果这个接口存在,那么固有元素的名字需要在JSX.IntrinsicElements接口的属性里查找。
declare namespace JSX {
interface IntrinsicElements {
foo: any
}
}
<foo />; // 正确
<bar />; // 错误在上例中,<foo />没有问题,但是<bar />会报错,因为它没在JSX.IntrinsicElements里指定。
注意:你也可以在
JSX.IntrinsicElements上指定一个用来捕获所有字符串索引:declare namespace JSX { interface IntrinsicElements { [elemName: string]: any; } }
4.2基于值的元素
基于值的元素会简单的在它所在的作用域里按标识符查找。
import MyComponent from "./myComponent";
<MyComponent />; // 正确
<SomeOtherComponent />; // 错误有两种方式可以定义基于值的元素:
- 无状态函数组件 (SFC)
- 类组件
由于这两种基于值的元素在JSX表达式里无法区分,因此TypeScript首先会尝试将表达式做为无状态函数组件进行解析。如果解析成功,那么TypeScript就完成了表达式到其声明的解析操作。如果按照无状态函数组件解析失败,那么TypeScript会继续尝试以类组件的形式进行解析。如果依旧失败,那么将输出一个错误。
4.3 无状态函数组件
正如其名,组件被定义成JavaScript函数,它的第一个参数是props对象。 TypeScript会强制它的返回值可以赋值给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>由于无状态函数组件是简单的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 {
...
}4.4 类组件
我们可以定义类组件的类型。
我们首先最好弄懂两个新的术语:元素类的类型和元素实例的类型。
现在有<Expr />,元素类的类型为Expr的类型。 所以在上面的例子里,如果MyComponent是ES6的类,那么类类型就是类的构造函数和静态部分。 如果MyComponent是个工厂函数,类类型为这个函数。
一旦建立起了类类型,实例类型由类构造器或调用签名(如果存在的话)的返回值的联合构成。 再次说明,在ES6类的情况下,实例类型为这个类的实例的类型,并且如果是工厂函数,实例类型为这个函数返回值类型。
class MyComponent {
render() {}
}
// 使用构造签名
var myComponent = new MyComponent();
// 元素类的类型 => MyComponent
// 元素实例的类型 => { render: () => void }
function MyFactoryFunction() {
return {
render: () => {
}
}
}
// 使用调用签名
var myComponent = MyFactoryFunction();
// 元素类的类型 => FactoryFunction
// 元素实例的类型 => { render: () => void }元素的实例类型很有趣,因为它必须赋值给JSX.ElementClass或抛出一个错误。 默认的JSX.ElementClass为{},但是它可以被扩展用来限制JSX的类型以符合相应的接口。
declare namespace JSX {
interface ElementClass {
render: any;
}
}
class MyComponent {
render() {}
}
function MyFactoryFunction() {
return { render: () => {} }
}
<MyComponent />; // 正确
<MyFactoryFunction />; // 正确
class NotAValidComponent {}
function NotAValidFactoryFunction() {
return {};
}
<NotAValidComponent />; // 错误
<NotAValidFactoryFunction />; // 错误4.5 属性类型检查
属性类型检查的第一步是确定元素属性类型。
这在固有元素和基于值的元素之间稍有不同。对于固有元素,这是JSX.IntrinsicElements属性的类型。
declare namespace JSX {
interface IntrinsicElements {
foo: { bar?: boolean }
}
}
// `foo`的元素属性类型为`{bar?: boolean}`
<foo bar />;对于基于值的元素,就稍微复杂些。 它取决于先前确定的在元素实例类型上的某个属性的类型。 至于该使用哪个属性来确定类型取决于JSX.ElementAttributesProperty。 它应该使用单一的属性来定义。 这个属性名之后会被使用。 TypeScript 2.8,如果未指定JSX.ElementAttributesProperty,那么将使用类元素构造函数或SFC调用的第一个参数的类型。
declare namespace JSX {
interface ElementAttributesProperty {
props; // 指定用来使用的属性名
}
}
class MyComponent {
// 在元素实例类型上指定属性
props: {
foo?: string;
}
}
// `MyComponent`的元素属性类型为`{foo?: string}`
<MyComponent foo="bar" />元素属性类型用于的JSX里进行属性的类型检查。 支持可选属性和必须属性。
declare namespace JSX {
interface IntrinsicElements {
foo: { requiredProp: string; optionalProp?: number }
}
}
<foo requiredProp="bar" />; // 正确
<foo requiredProp="bar" optionalProp={0} />; // 正确
<foo />; // 错误, 缺少 requiredProp
<foo requiredProp={0} />; // 错误, requiredProp 应该是字符串
<foo requiredProp="bar" unknownProp />; // 错误, unknownProp 不存在
<foo requiredProp="bar" some-unknown-prop />; // 正确, `some-unknown-prop`不是个合法的标识符️注意:如果一个属性名不是个合法的JS标识符(像
data-*属性),并且它没出现在元素属性类型里时不会当做一个错误。
另外,JSX还会使用JSX.IntrinsicAttributes接口来指定额外的属性,这些额外的属性通常不会被组件的props或arguments使用 - 比如React里的key。还有,JSX.IntrinsicClassAttributes<T>泛型类型也可以用来做同样的事情。这里的泛型参数表示类实例类型。在React里,它用来允许Ref<T>类型上的ref属性。通常来讲,这些接口上的所有属性都是可选的,除非你想要用户在每个JSX标签上都提供一些属性。
延展操作符也可以使用:
var props = { requiredProp: 'bar' };
<foo {...props} />; // 正确
var badProps = {};
<foo {...badProps} />; // 错误4.6 子孙类型检查
children是元素属性(attribute)类型的一个特殊属性(property),子JSXExpression将会被插入到属性里。 与使用JSX.ElementAttributesProperty来决定props名类似,我们可以利用JSX.ElementChildrenAttribute来决定children名。 JSX.ElementChildrenAttribute应该被声明在单一的属性(property)里。
declare namespace JSX {
interface ElementChildrenAttribute {
children: {}; // specify children name to use
}
}如不特殊指定子孙的类型,我们将使用React typings里的默认类型。
<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>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>5. JSX结果类型
默认地JSX表达式结果的类型为any。
你可以自定义这个类型,通过指定JSX.Element接口。 然而,不能够从接口里检索元素,属性或JSX的子元素的类型信息。 它是一个黑盒。
6.嵌入的表达式
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>7.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" />; // 正确
<MyComponent foo={0} />; // 错误8.工厂函数
jsx: react编译选项使用的工厂函数是可以配置的。可以使用jsxFactory命令行选项,或内联的@jsx注释指令在每个文件上设置。比如,给createElement设置jsxFactory,<div />会使用createElement("div")来生成,而不是React.createElement("div")。
注释指令可以像下面这样使用(在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定义(默认),编译器会先检查React.JSX,之后才检查全局的JSX。如果工厂函数定义为h,那么在检查全局的JSX之前先检查h.JSX。
边栏推荐
- Mistakes in Brushing the Questions 1-Implicit Conversion and Loss of Precision
- R language ggplot2 visualization: use the ggtexttable function of the ggpubr package to visualize tabular data (directly draw tabular graphs or add tabular data to images), use tbody_add_border to add
- 【New Edition】DeepFakes: Creation, Detection and Influence
- WPF 截图控件之文字(七)「仿微信」
- 从零开始入门单片机(一):必会背景知识总结
- Using the TCP protocol, will there be no packet loss?
- 行为型模式-模板方法模式
- R language ggplot2 visualization: use the ggbarplot function of the ggpubr package to visualize the horizontal column chart (bar chart), use the orientation parameter to set the column chart to be tra
- 李航《统计学习方法》笔记之感知机perceptron
- Linux system uninstall, install, upgrade, migrate clickHouse database
猜你喜欢

HikariCP database connection pool, too fast!

In the whole development of chi V853 board tried to compile QT test

MySql千万级分页优化,快速插入千万数据方法

You Only Hypothesize Once: 用旋转等变描述子估计变换做点云配准(已开源)

用汇编实现爱心特效【七夕来袭】

关于缓存数据的探讨

【New Edition】DeepFakes: Creation, Detection and Influence

第十六章 协程

Use the scrapy to climb to save data to mysql to prevent repetition

Verilog的随机数系统任务----$random
随机推荐
npm ERR! 400 Bad Request - PUT xxx - Cannot publish over previously published version “1.0.0“.
适配器模式适配出栈和队列及优先级队列
一文带你了解推荐系统常用模型及框架
行为型模式-策略模式
npm ERR! 400 Bad Request - PUT xxx - Cannot publish over previously published version “1.0.0“.
The heavyweights are coming!Spoilers for the highlights of the Alibaba Cloud Life Science and Intelligent Computing Summit
2022.7.25-7.31 AI行业周刊(第108期):值钱比赚钱更重要
Verilog的随机数系统任务----$random
Chapter 15 Generics
Application scenarios of js anti-shake function and function throttling
matlab-day02
重磅大咖来袭!阿里云生命科学与智能计算峰会精彩内容剧透
R语言ggplot2可视化:使用ggpubr包的ggtexttable函数可视化表格数据(直接绘制表格图或者在图像中添加表格数据)、使用tbody_add_border为表格中的表头添加外侧框线
R语言ggpubr包的ggbarplot函数可视化分组柱状图、设置add参数为mean_se可视化不同水平均值的柱状图并为柱状图添加误差线(se标准误差)、position参数自定义分组柱状图分离
Use the scrapy to climb to save data to mysql to prevent repetition
牛客网项目17节生成验证码 刷新验证码一直没反应
读博一年后对机器学习工程的思考
MySql tens of millions of paging optimization, fast insertion method of tens of millions of data
R language time series data arithmetic operation: use the log function to log the time series data, and use the diff function to calculate the successive difference of the logarithmic time series data
行为型模式-模板方法模式