Young Kbt blog Young Kbt blog
首页
  • java基础

    • Java基础
    • Java集合
    • Java反射
    • JavaJUC
    • JavaJVM
  • Java容器

    • JavaWeb
  • Java版本新特性

    • Java新特性
  • SQL 数据库

    • MySQL
    • Oracle
  • NoSQL 数据库

    • Redis
    • ElasticSearch
  • 数据库

    • MyBatis
    • MyBatis-Plus
  • 消息中间件

    • ActiveMQ
    • RabbitMQ
    • RocketMQ
    • Kafka
  • 进阶服务

    • Nginx
  • Spring
  • Spring Boot
  • Spring Security
  • 设计模式
  • 算法
  • 知识
  • 管理

    • Maven
    • Git
  • 部署

    • Linux
    • Docker
    • Jenkins
    • Kubernetes
  • 进阶

    • TypeScript
  • 框架

    • React
    • Vue2
    • Vue3
  • 轮子工具
  • 项目工程
  • 友情链接
  • 本站

    • 分类
    • 标签
    • 归档
  • 我的

    • 收藏
    • 关于
    • Vue2-Admin (opens new window)
    • Vue3-Admin(完善) (opens new window)
GitHub (opens new window)

Shp Liu

朝圣的使徒,正在走向编程的至高殿堂!
首页
  • java基础

    • Java基础
    • Java集合
    • Java反射
    • JavaJUC
    • JavaJVM
  • Java容器

    • JavaWeb
  • Java版本新特性

    • Java新特性
  • SQL 数据库

    • MySQL
    • Oracle
  • NoSQL 数据库

    • Redis
    • ElasticSearch
  • 数据库

    • MyBatis
    • MyBatis-Plus
  • 消息中间件

    • ActiveMQ
    • RabbitMQ
    • RocketMQ
    • Kafka
  • 进阶服务

    • Nginx
  • Spring
  • Spring Boot
  • Spring Security
  • 设计模式
  • 算法
  • 知识
  • 管理

    • Maven
    • Git
  • 部署

    • Linux
    • Docker
    • Jenkins
    • Kubernetes
  • 进阶

    • TypeScript
  • 框架

    • React
    • Vue2
    • Vue3
  • 轮子工具
  • 项目工程
  • 友情链接
  • 本站

    • 分类
    • 标签
    • 归档
  • 我的

    • 收藏
    • 关于
    • Vue2-Admin (opens new window)
    • Vue3-Admin(完善) (opens new window)
GitHub (opens new window)
  • 超文本标记语言 - Html

  • 解释编程语言 - JavaScript

  • JS 超集语言 - TypeScript

    • TypeScript - 介绍
    • TypeScript - 安装和使用
    • TypeScript - 基本类型
    • TypeScript - 编译和配置
    • TypeScript - 文件打包
    • TypeScript - 接口
    • TypeScript - 函数
    • TypeScript - 类
    • TypeScript - 泛型
    • TypeScript - 类型推断
    • TypeScript - 高级类型
    • TypeScript Office - 入门
    • TypeScript Office - 常用类型
    • TypeScript Office - 类型缩小
    • TypeScript Office - 更多函数
    • TypeScript Office - 对象类型
    • TypeScript Office - 类型操纵
    • TypeScript Office - 类
    • TypeScript Office - 模块
    • TypeScript Office - 变量声明
    • TypeScript Office - 类型推断
    • TypeScript Office - 枚举
    • TypeScript Office - 公共类型
    • TypeScript Office - Symbols
    • TypeScript Office - 类型兼容性
    • TypeScript Office - 迭代器和生成器
    • TypeScript Office - 装饰器
    • TypeScript Office - JSX
      • JSX
      • 基本用法
      • as 操作符
      • 类型检查
      • 内在元素
      • 基于值的元素
        • 函数组件
        • 类组件
      • 属性类型检查
      • 子类型检查
      • JSX 的结果类型
      • 嵌入表达式
      • React 集成
      • 配置 JSX
    • TypeScript Office - 混入
    • TypeScript Office - 三斜线指令
    • TypeScript Office - 模块进阶
    • TypeScript Office - 模块解析
    • TypeScript Office - 命名空间
    • TypeScript Office - 命名空间与模块
    • TypeScript Office - 声明合并
  • 界面构建框架 - React

  • 渐进式框架 - Vue2

  • 渐进式框架 - Vue3

  • 前端
  • JS 超集语言 - TypeScript
Young Kbt
2022-09-15
目录

TypeScript Office - JSX

  • JSX
  • 基本用法
  • as 操作符
  • 类型检查
  • 内在元素
  • 基于值的元素
    • 函数组件
    • 类组件
  • 属性类型检查
  • 子类型检查
  • JSX 的结果类型
  • 嵌入表达式
  • React 集成
  • 配置 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 模式相当于保留模式,它保留了所有的 JSX,但输出将有一个 .js 文件扩展名。

Mode Input Output Output File Extension
preserve <div /> <div /> .jsx
react <div /> React.createElement("div") .js
react-native <div /> <div /> .js
react-jsx <div /> _jsx("div", {}, void 0); .js
react-jsxdev <div /> _jsxDEV("div", {}, void 0, false, {...}, this); .js

你可以使用 jsx 命令行标志或你的 tsconfig.json 文件中的相应选项 jsx 指定这种模式。

注意:你可以as操作符用 jsxFactory 选项指定针对 react JSX 生成 JS 时使用的 JSX 工厂函数(默认为 React.createElement )。

# as 操作符

回忆一下如何编写类型断言。

const foo = <foo>bar;
1

这断言变量 bar 具有 foo 类型。由于 TypeScript 也使用角括号进行类型断言,将其与 JSX 的语法相结合会带来某些解析困难。因此,TypeScript 不允许在 .tsx 文件中使用角括号类型断言。

由于上述语法不能在 .tsx 文件中使用,应该使用一个替代的类型断言操作符:as。这个例子可以很容易地用 as 操作符重写。

const foo = bar as foo;
1

as 操作符在 .ts 和 .tsx 文件中都可用,并且在行为上与角括号式断言风格相同。

# 类型检查

为了理解 JSX 的类型检查,你必须首先理解内在元素和基于值的元素之间的区别。给定一个 JSX 表达式,expr 既可以指环境中固有的东西(例如 DOM 环境中的 div 或 span),也可以指你创建的 自定义组件。这很重要,有两个原因:

  • 对于 React 来说,内在元素是以字符串的形式发出的 React.createElement("div"),而你创建的组件则不是 React.createElement(MyComponent)
  • 在 JSX 元素中传递的属性类型应该被不同地查找。元素的内在属性应该是已知的,而组件可能想要指定他们自己的属性集

TypeScript 使用与 React 相同的约定 来区分这些。一个内在的元素总是以小写字母开始,而一个基于价值的元素总是以大写字母开始。

# 内在元素

内在元素在特殊接口 JSX.IntrinsicElements 上被查询到。默认情况下,如果没有指定这个接口,那么什么都可以,内在元素将不会被类型检查。然而,如果这个接口存在,那么内在元素的名称将作为 JSX.IntrinsicElements 接口上的一个属性被查询。比如说。

declare namespace JSX {
    interface IntrinsicElements {
        foo: any;
    }
}
<foo />; // 正确
<bar />; // 错误
1
2
3
4
5
6
7

在上面的例子中,<foo /> 可以正常工作,但会导致一个错误,因为它没有被指定在 JSX.IntrinsicElements 上。

注意:你也可以在 JWX.IntrinsicElements 上指定一个全面的字符串索引器,如下所示:

declare namespace JSX {
    interface IntrinsicElements {
        [elemName: string]: any;
    }
}
1
2
3
4
5

# 基于值的元素

基于值的元素只是通过范围内的标识符进行查询。

import MyComponent from "./myComponent";
<MyComponent />; // 正确
<SomeOtherComponent />; // 错误
1
2
3

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

  • 函数组件(FC)
  • 类组件

因为这两类基于值的元素在 JSX 表达式中是无法区分的,首先TS尝试使用重载解析将表达式解析为一个函数组件。如果这个过程成功了,那么 TS 就完成了将表达式解析为它的声明。如果该值不能被解析为一个函数组件,那么 TS 将尝试将其解析为一个类组件。如果失败了,TS 将报告一个错误。

# 函数组件

顾名思义,该组件被定义为一个 JavaScript 函数,其第一个参数是一个 props 对象。TS强制要求它的返回类型必须是可分配给 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 />
);
1
2
3
4
5
6
7
8
9
10
11
12

因为函数组件只是一个 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;
function MainButton(prop: ClickableProps): JSX.Element {
    // ...
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

注意:函数组件以前被称为无状态函数组件(SFC)。由于 Function Components 在最近的 react 版本中不再被认为是无状态的,SFC 类型和它的别名 StatelessComponent 被废弃了。

# 类组件

定义一个类组件的类型是可能的。然而,要做到这一点,最好理解两个新术语:元素类类型和元素实例类型。

给定 <Expr />,元素类的类型就是 Expr 的类型。所以在上面的例子中,如果 MyComponent 是一个 ES6 类,那么类的类型就是该类的构造函数和状态。如果 MyComponent 是一个工厂函数,类的类型将是该函数。

一旦类的类型被确定,实例的类型就由该类的构造或调用签名(无论哪一个)的返回类型的联合决定。因此,在 ES6 类的情况下,实例类型将是该类实例的类型,而在工厂函数的情况下,它将是该函数返回值的类型。

class MyComponent {
    render() {}
}
// 使用构造签名
const myComponent = new MyComponent();
// 元素类类型 => MyComponent
// 元素实例类型 => { render: () => void }
function MyFactoryFunction() {
    return {
        render: () => {},
    };
}
// 使用调用签名
const myComponent = MyFactoryFunction();
// 元素类类型 => MyFactoryFunction
// 元素实例类型 => { render: () => void }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

元素实例类型很有趣,因为它必须可以分配给 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 />; // 错误
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 属性类型检查

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

对于内在元素,它是 JSX.IntrinsicElements 上的属性类型。

declare namespace JSX {
    interface IntrinsicElements {
        foo: { bar?: boolean };
    }
}
// 'foo'的元素属性类型是'{bar?: boolean}'
<foo bar />;
1
2
3
4
5
6
7

元素属性类型是用来对 JSX 中的属性进行类型检查的。支持可选和必需的属性。

declare namespace JSX {
    interface IntrinsicElements {
    	foo: { requiredProp: string; optionalProp?: number };
    }
}
1
2
3
4
5
<foo requiredProp="bar" />; // 正确
<foo requiredProp="bar" optionalProp={0} />; // 正确
<foo />; // 错误, requiredProp 缺失
<foo requiredProp={0} />; // 错误, requiredProp 应该为 string 类型
<foo requiredProp="bar" unknownProp />; // 错误, unknownProp 属性不存在
<foo requiredProp="bar" some-unknown-prop />; // 正确, 因为 'some-unknown-prop' 不是一个有效的属性标识
1
2
3
4
5
6

注意:如果一个属性名称不是一个有效的 JS 标识符(如 data-* 属性),如果在元素属性类型中找不到它,则不被认为是一个错误。

此外,JSX.IntrinsicAttributes 接口可以用来指定 JSX 框架使用的额外属性,这些属性一般不会被组件的道具或参数使用,例如 React 中的 key。进一步专门化,通用的 JSX.IntrinsicClassAttributes 类型也可以用来为类组件(而不是函数组件)指定同种额外属性。在这种类型中,通用参数与类的实例类型相对应。在 React 中,这被用来允许 Ref 类型的 ref 属性。一般来说,这些接口上的所有属性都应该是可选的,除非你打算让你的 JSX 框架的用户需要在每个标签上提供一些属性。

展开运算符也能正常工作:

const props = { requiredProp: "bar" };
<foo {...props} />;  // 正确
const badProps = {};
<foo {...badProps} />;  // 错误
1
2
3
4

# 子类型检查

在 TypeScript 2.3 中,TS 引入了 children 的类型检查。children 是元素属性类型中的一个特殊属性,子的 JSXExpressions 被采取插入属性中。类似于 TS 使用 JSX.ElementAttributesProperty 来确定 props 的 名称,TS 使用 JSX.ElementChildrenAttribute 来确定这些 props 中的 children 的名称。JSX.ElementChildrenAttribute 应该用一个单一的属性来声明。

declare namespace JSX {
    interface ElementChildrenAttribute {
        children: {}; // 指定要使用的 children 名称
    }
}
1
2
3
4
5
<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>
1
2
3
4
5
6
7
8
9
10
11
12

你可以像其他属性一样指定 children 的类型。这将覆盖默认的类型,例如,如果你使用 React 类型的话:

interface PropsType {
    children: JSX.Element
    name: string
}
class Component extends React.Component<PropsType, {}> {
    render() {
        return (
            <h2>
            {this.props.children}
            </h2>
        )
    }
}

// 正确
<Component name="foo">
<h1>Hello World</h1>
</Component>
// 错误: children是JSX.Element的类型,而不是JSX.Element的数组
<Component name="bar">
<h1>Hello World</h1>
<h2>Hello World</h2>
</Component>
// 错误: children是JSX.Element的类型,而不是JSX.Element的数组或字符串。
<Component name="baz">
<h1>Hello</h1>
World
</Component>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

# JSX 的结果类型

默认情况下,JSX 表达式的结果被打造成 any 类型。你可以通过指定 JSX.Element 接口来定制类型。然而,不可能从这个接口中检索到关于 JSX 的元素、属性或孩子的类型信息。它是一个黑盒子。

# 嵌入表达式

JSX 允许你通过用大括号 { } 包围表达式,在标签之间嵌入表达式。

const a = (
    <div>
        {["foo", "bar"].map((i) => (
            <span>{i / 2}</span>
        ))}
    </div>
);
1
2
3
4
5
6
7

上面的代码将导致一个错误,因为你不能用一个字符串除以一个数字。当使用 preserve 选项时,输出结果看起来像:

const a = (
    <div>
        {["foo", "bar"].map(function (i) {
            return <span>{i / 2}</span>;
        })}
    </div>
);
1
2
3
4
5
6
7

# React 集成

要在 React 中使用 JSX,你应该使用 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} />; // 错误
1
2
3
4
5
6
7
8
9
10
11

# 配置 JSX

有多个编译器标志可以用来定制你的 JSX,它们既可以作为编译器标志,也可以通过内联的每个文件实用程序发挥作用。

编辑此页 (opens new window)
#TypeScript
更新时间: 2023/09/18, 16:34:13
TypeScript Office - 装饰器
TypeScript Office - 混入

← TypeScript Office - 装饰器 TypeScript Office - 混入→

最近更新
01
技术随笔 - Element Plus 修改包名 原创
11-02
02
Reactor - 扩展性
11-02
03
Reactor - 最佳实践
11-02
更多文章>
Theme by Vdoing | Copyright © 2021-2024 Young Kbt | blog
桂ICP备2021009994号
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式