介绍
在线的免费的代码转换器工具:可以把HTML 代码移植到 JSX 语法。还支持很多其他的转换。
JSX 是 ECMAScript 的一种类似 XML 的语法扩展,没有任何定义的语义。它不打算由引擎或浏览器实现。它并不是将 JSX 纳入 ECMAScript 规范本身的提议。它旨在供各种预处理器(转译器)使用这些标记转换为标准 ECMAScript。
JSX 是一种在 JavaScript 中编写类似 XML 结构来描述用户界面的语法。
TypeScript支持内嵌,类型检查以及将JSX直接编译为JavaScript。
基本用法
想要使用JSX必须做两件事:
- 给文件一个
.tsx
扩展名:- 向开发工具和编译器表明该文件中可能包含 JSX 语法。
- 启用jsx选项
- 启用 jsx 选项通常是在
tsconfig.json
文件中进行配置。
- 启用 jsx 选项通常是在
TypeScript具有三种JSX模式:
preserve
:保留 JSX 为原始的样子,不进行转换。- 保留 JSX 为原始的样子,不进行转换。
- 输出文件会带有
.jsx
扩展名。
react
:这是在使用 React 框架时常用的选项,会对 JSX 进行相应的转换。- 生成
React.createElement
,在使用前不需要再进行转换操作。 - 输出文件的扩展名为
.js
。
- 生成
react-native
:用于 React Native 开发。- 保留 JSX 为原始的样子,不进行转换。
- 输出文件的扩展名为
.js
。
模式 | 输入 | 输出 | 输出文件扩展名 |
---|---|---|---|
preserve | <div /> | <div /> | .jsx |
react | <div /> | React.createElement("div") | .jx |
react-native | <div /> | <div /> | .jx |
这些模式只在代码生成阶段起作用,类型检查并不受影响。
注意:React
标识符是写死的硬编码,所以你必须保证React(大写的R)是可用的。
以下是一个简单的 tsconfig.json
配置示例,启用了 jsx
选项为 react
:
{
"compilerOptions": {
"jsx": "react"
}
}
在具有 .tsx
扩展名的文件中,就可以自由地使用 JSX 语法进行开发了。
JSX 中的属性设置与 HTML 类似,但需要使用驼峰命名法。比如在 HTML 中是 class
属性,在 JSX 中则是 className
。
const element = <div className="my-class">Content</div>;
可以通过使用引号""
,来将属性值指定为字符串字面量:
const element = <div className="my-class" title="This is a title">Content</div>;
className
的值 my-class
和 title
的值 This is a title
都是字符串字面量。
这种方式适用于属性值明确为固定字符串的情况。
如果属性值是动态计算的结果或者是变量,使用{}
直接将变量或表达式嵌入到 JSX 中:
const titleText = 'Dynamic Title';
const element = <div className="my-class" title={titleText}>Content</div>;
JSX 允许在模板中插入数组,数组会自动展开所有成员:
let arr = [
<p>111</p>,
<p>222</p>,
];
const content = (
<div>{arr}</div>
);
JSX 支持条件渲染和循环渲染。
条件渲染:
const isLoggedIn = true;
const element = (
<div>
{isLoggedIn? <p>Welcome back!</p> : <p>Please log in.</p>}
</div>
);
循环渲染:
const items = ['Apple', 'Banana', 'Orange'];
const element = (
<ul>
{items.map(item => <li>{item}</li>)}
</ul>
);
JSX 的注释代码需要写在花括号 {}
中:
const content = (
{/*注释...*/}
<h1>hello world</h1>
);
as
操作符
在 TypeScript 的 JSX 中,as
操作符通常用于类型断言。
TypeScript也使用尖括号<>
来表示类型断言:
let foo = <foo>bar;
这句代码断言bar
变量是foo
类型的。
TypeScript在.tsx
文件里禁用了使用尖括号<>
的类型断言。
因为在 TypeScript 的 JSX 环境中(例如 .tsx
文件),尖括号<>
有特殊用途。
使用 as
操作符:
let foo = bar as foo;
JSX 环境中使用 as
操作符:
interface CustomButtonProps {
label: string;
onClick: () => void;
}
const button = (
<button
as={CustomButton}
label="Click Me"
onClick={() => console.log('Clicked!')}
/>
);
as
操作符在.ts
和.tsx
里都可用,并且与尖括号类型断言行为是等价的。
类型检查
TypeScript 对 JSX 的类型检查涵盖了元素类型、属性类型、子元素类型以及基于值的元素类型等多个方面,通过严格的类型检查可以在开发过程中及早发现类型不匹配的问题,提高代码的质量和可维护性。
元素类型检查
元素类型检查分为固有元素、和自定义组件。
固有元素(内置的 HTML 元素)
在 TypeScript 中对 JSX 固有元素(即内置的 HTML 元素)的检查主要包括以下几个方面:
- TypeScript 会检查使用的固有元素名称是否正确。
- 必须使用正确的 HTML 元素名称,如
<div>
、<p>
、<span>
等。使用错误或不存在的元素名称会导致编译错误。
- 必须使用正确的 HTML 元素名称,如
// 以下使用是合法的
<input type="text" placeholder="Enter text" />
// 以下使用会引发错误
<myInvalidElement /> // 不存在这样的固有元素
- 对于固有元素的属性,TypeScript 会根据 HTML 规范进行类型检查。
- 每个固有元素都有一组合法的属性,并且这些属性的值必须符合相应的类型。
示例:<img src="image.jpg" alt="An image" />
中,src
属性必须是一个有效的图像路径或 URL,alt
属性必须是一个字符串。
const element2 = <img src="valid-image.jpg" alt="Valid image" />; // 合法
const element4 = <img src={123} alt="Invalid" />; // 不合法, src 应该是字符串,而不是数字
自定义组件
对于自定义组件,必须确保组件已经正确定义并且在使用的作用域内可访问。TypeScript 会检查传递给自定义组件的属性是否符合组件定义的属性类型。
例如,定义一个名为 MyComponent
的自定义组件,接收一个 name
属性,类型为字符串:
interface MyComponentProps {
name: string;
}
function MyComponent({ name }: MyComponentProps) {
return <div>Hello, {name}!</div>;
}
使用时:
// 正确的使用
const element5 = <MyComponent name="John" />;
// 错误的使用,会产生编译错误
const element6 = <MyComponent name={123} />; // name 应该是字符串,而不是数字
基于值的元素类型检查
在 JSX 中,基于值的元素可能是无状态函数组件或类组件。TypeScript 会首先尝试将表达式作为无状态函数组件进行解析,如果解析成功,就完成表达式到其声明的解析操作。如果按照无状态函数组件解析失败,会继续尝试以类组件的形式进行解析。如果依旧失败,就会输出一个错误。
无状态函数组件(SFC)
组件被定义成JavaScript函数,它的第一个参数是props
对象。 TypeScript会强制它的返回值可以赋值给JSX.Element
。
function MyComponent({ name }: { name: string }) {
return <div>Hello, {name}!</div>;
}
在使用时,可以直接在 JSX 表达式中通过标识符引用这个组件:
<MyComponent name="John" />
由于无状态函数组件是简单的JavaScript函数,所以可以利用函数重载:
// ClickableProps 接口定义了一个名为 children 的属性,可以是单个 JSX.Element 或者是 JSX.Element 的数组。
interface ClickableProps {
children: JSX.Element[] | JSX.Element
}
// HomeProps 接口继承自 ClickableProps,并添加了一个名为 home 的属性,类型为 JSX.Element。
interface HomeProps extends ClickableProps {
home: JSX.Element;
}
// SideProps 接口也继承自 ClickableProps,添加了一个名为 side 的属性,类型是 JSX.Element 或者 字符串。
interface SideProps extends ClickableProps {
side: JSX.Element | string;
}
函数实现示例:
function MainButton(prop: HomeProps): JSX.Element;
function MainButton(prop: SideProps): JSX.Element {
if ('home' in prop) {
// 处理 HomeProps 类型的参数
return <button>{prop.home}</button>;
} else if ('side' in prop) {
// 处理 SideProps 类型的参数
return (
<button>{typeof prop.side === 'string' ? prop.side : prop.side}</button>
);
}
return null;
}
MainButton
函数被重载为接受两种不同类型的参数,分别是 HomeProps
类型和 SideProps
类型,并且返回值都是 JSX.Element
。
类组件
当定义类组件时,可以分别考虑元素类的类型和元素实例的类型。
元素类的类型和实例类型的概念:
- 对于 JSX 表达式
<Expr />
,元素类的类型就是Expr
的类型。如果Expr
是一个 ES6 类,那么类类型包括类的构造函数和静态部分。如果Expr
是一个工厂函数,类类型就是这个函数本身。 - 一旦确定了类类型,实例类型由类构造器或调用签名(如果存在的话)的返回值的联合构成。对于 ES6 类,实例类型是这个类的实例的类型;对于工厂函数,实例类型是这个函数的返回值类型。
示例:
class MyComponent {
render() {}
}
// 使用构造签名
var myComponent = new MyComponent();
// 元素类的类型 => MyComponent
// 元素实例的类型 => { render: () => void }
function MyFactoryFunction() {
return {
render: () => {
}
}
}
// 使用调用签名
var myComponent = MyFactoryFunction();
// 元素类的类型 => FactoryFunction
// 元素实例的类型 => { render: () => void }
示例中的类型分析:
- 在给出的示例中,对于
MyComponent
类:- 元素类的类型是
MyComponent
,也就是这个类本身。 - 元素实例的类型是
{ render: () => void }
,因为这个类有一个render
方法。
- 元素类的类型是
- 对于
MyFactoryFunction
:- 元素类的类型是这个工厂函数本身。
- 元素实例的类型也是
{ render: () => void }
,因为这个函数返回一个对象,该对象有一个render
方法。
元素的实例类型必须赋值给JSX.ElementClass
或抛出一个错误。
JSX.ElementClass
的作用:
- 默认情况下,
JSX.ElementClass
为{}
,这意味着在没有自定义时,JSX 元素的实例类型可以是任何对象。 - 可以通过扩展
JSX.ElementClass
来限制 JSX 的类型以符合相应的接口。 - 当定义了特定的
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 />; // 错误
在代码中通过 declare namespace JSX
并定义 interface ElementClass
为 { render: any; }
,这就要求 JSX 元素的实例必须有一个 render
方法。
MyComponent
和 MyFactoryFunction
是有效的,因为它们创建的对象都有 render
方法,符合 JSX.ElementClass
的要求。
NotAValidComponent
和 NotAValidFactoryFunction
是无效的,因为它们创建的对象没有 render
方法,不满足 JSX.ElementClass
的要求,所以在 JSX 中使用时会产生错误。
通过理解元素类的类型、实例类型以及 JSX.ElementClass
的作用,可以更好地控制和规范在 TypeScript 中使用 JSX 时的组件类型,提高代码的类型安全性和可维护性。
属性类型检查
属性类型检查的第一步是确定元素属性类型。 这在固有元素和基于值的元素之间稍有不同。
固有元素的属性类型检查
对于固有元素,其属性类型由 JSX.IntrinsicElements
接口定义。
declare namespace JSX {
interface IntrinsicElements {
foo: { bar?: boolean };
}
}
// `foo`的元素属性类型为`{bar?: boolean}`
<foo bar />;
<foo>
元素的属性类型被明确为可以有一个可选的 bar
属性,类型为布尔值。
固有元素的属性类型检查
- 确定属性类型的方式:
- 对于基于值的元素(如类组件或无状态函数组件),属性类型的确定稍微复杂一些。它取决于先前确定的在元素实例类型上的某个属性的类型,而具体使用哪个属性来确定类型取决于
JSX.ElementAttributesProperty
的定义。 - 如果未指定
JSX.ElementAttributesProperty
,在 TypeScript 2.8 及以后版本中,将使用类元素构造函数或无状态函数组件调用的第一个参数的类型。
declare namespace JSX {
interface ElementAttributesProperty {
props; // 指定用来使用的属性名
}
}
class MyComponent {
props: {
foo?: string;
};
}
// `MyComponent`的元素属性类型为`{foo?: string}`
<MyComponent foo="bar" />;
- 支持可选属性和必须属性:
元素属性类型可以定义可选属性和必须属性。
示例:
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`不是个合法的标识符
在这个例子中,<foo>
元素有一个必须的 requiredProp
属性,类型为字符串,还有一个可选的 optionalProp
属性,类型为数字。
额外属性和泛型类型
JSX.IntrinsicAttributes
接口:JSX.IntrinsicAttributes
接口可以用来指定额外的属性,这些属性通常不会被组件的props
或arguments
使用。例如在 React 中,key
属性就是通过这种方式指定的。
declare namespace JSX {
interface IntrinsicAttributes {
// 添加 data-testid 属性作为额外属性
'data-testid'?: string;
}
}
function MyComponent() {
return <div data-testid="my-component-test-id">Hello World</div>;
}
const AnotherComponent = () => {
return <p data-testid="another-component-test-id">Another Component</p>;
};
在示例中,通过JSX.IntrinsicAttributes
接口定义了一个可选的data-testid
属性,这个属性可以在任何 JSX 元素上使用,而不需要在每个组件的 props
中明确声明。
JSX.IntrinsicClassAttributes<T>
泛型类型。
假设我们有一个类组件,需要支持 React 的ref
属性来获取对 DOM 元素的引用。我们可以使用JSX.IntrinsicClassAttributes<T>
泛型类型来实现。
import React, { Component } from 'react';
interface MyComponentProps {
title: string;
}
class MyClassComponent extends Component<MyComponentProps> {
render() {
return <div>{this.props.title}</div>;
}
}
declare namespace JSX {
interface IntrinsicClassAttributes<MyClassComponent> {
// 允许 ref 属性
ref?: React.Ref<MyClassComponent>;
}
}
现在可以在使用MyClassComponent
时传递ref
属性:
const ref = React.createRef<MyClassComponent>();
<MyClassComponent title="My Title" ref={ref} />;
在这个例子中,使用JSX.IntrinsicClassAttributes<T>
泛型类型为MyClassComponent
类添加了一个可选的ref
属性,这样在使用这个类组件时就可以方便地获取对组件实例的引用。
延展操作符的使用
延展操作符可以方便地将一个对象的属性展开到 JSX 元素中。
示例:
let props = { requiredProp: 'bar' };
<foo {...props} />; // 正确
let badProps = {};
<foo {...badProps} />; // 错误
JSX.IntrinsicElements
接口的作用
JSX.IntrinsicElements
接口用于定义和查找固有元素(内置 HTML 元素)的类型信息,从而进行类型检查。
JSX.IntrinsicElements
接口定义了各种固有元素及其可接受的属性和属性类型。
当在 TypeScript 中处理 JSX 时,如果没有明确指定 JSX.IntrinsicElements
接口,不对固有元素进行类型检查。这时,使用了不正确或不被支持的属性,也可能不会在编译时产生类型错误。
如果定义了 JSX.IntrinsicElements
接口,对于每个固有元素的名称及其可接受的属性,都需要在这个接口中进行查找和匹配。
例如,如果定义了如下的 JSX.IntrinsicElements
接口:
interface CustomIntrinsicElements {
'button': {
disabled: boolean;
onClick: (event: MouseEvent) => void;
};
}
declare global {
namespace JSX {
interface IntrinsicElements extends CustomIntrinsicElements {}
}
}
在上述示例中,为 button
元素自定义了其 disabled
属性必须是布尔类型,onClick
属性必须是特定类型的函数。 customStr
是自定义的属性,必须是字符串类型。
这样,当在代码中使用 <button>
元素时,如果属性的类型不符合定义,TypeScript 就会在编译时给出错误提示。
通过扩展 JSX.IntrinsicElements
,可以添加自定义的固有元素或者修改已有固有元素的默认行为:
interface CustomIntrinsicElements {
// 添加自定义固有元素
'myCustomElement': {
customProp1: string;
customProp2: number;
};
// 修改已有固有元素的默认行为
'button': {
// 在这里添加、修改或覆盖按钮元素的属性类型
disabled: boolean;
customButtonProp: string;
};
}
declare global {
namespace JSX {
interface IntrinsicElements extends CustomIntrinsicElements {}
}
}
在组件中使用自定义或修改后的固有元素:
function MyComponent() {
return (
<div>
<myCustomElement customProp1="Value1" customProp2={123} />
<button disabled={true} customButtonProp="Custom Button Prop Value" >Click Me</button>
</div>
);
}
也可以在JSX.IntrinsicElements
上指定一个用来捕获所有字符串索引:
declare namespace JSX {
interface IntrinsicElements {
[elemName: string]: any;
}
}
这段 TypeScript 代码通过 declare namespace JSX
声明了一个与 JSX
相关的命名空间。在这个命名空间中,定义了 IntrinsicElements
接口。
IntrinsicElements
接口中,[elemName: string]: any;
这部分表示对于任意字符串形式的元素名称(elemName
),其对应的属性类型可以是任意类型(any
)。
使用:
function MyComponent() {
return <customElement someRandomProp="value" />;
}
不会导致类型错误,因为对于任何未明确定义的固有元素,其属性类型被允许为任意类型。
JSX.IntrinsicAttributes
JSX.IntrinsicAttributes
用于表示 JSX 元素的内置属性类型。
当使用 JSX 语法时,TypeScript 需要确定每个元素的属性类型。
- 对于固有元素(内置的 HTML 元素),TypeScript 内置了对这些元素常见属性的类型定义。
- 对于自定义组件,
JSX.IntrinsicAttributes
可以用来描述可能传递给组件的通用属性,这些属性不是由组件明确定义的属性,而是类似style
、className
、key
等在 JSX 中经常使用的属性。
示例:
interface MyComponentProps {
customProp: string;
}
function MyComponent({
customProp
}: MyComponentProps & JSX.IntrinsicAttributes) {
return (
<div className={/*...*/} style={/*...*/}>
{customProp}
</div>
);
}
在示例中,MyComponent
组件除了接收自定义的 customProp
属性外,还可以接收 JSX.IntrinsicAttributes
中定义的那些通用属性,如 className
和 style
。
如果不使用 JSX.IntrinsicAttributes
,当尝试在使用 MyComponent
时传递 className
或 style
等属性时,TypeScript 会报错,因为这些属性没有在 MyComponentProps
接口中定义。
JSX.IntrinsicAttributes
提供了一种方便的方式来处理在 JSX 中传递给组件的通用属性,确保类型安全。
子孙类型检查
从TypeScript 2.3开始,引入了children类型检查。
在 JSX 中,children 是元素属性类型中的一个特殊属性。当在 JSX 结构中为一个组件或元素指定子元素时,这些子元素会被插入到 children 属性中进行处理。
JSX.ElementAttributesProperty
:用于决定组件接收的属性名称。例如,可以通过自定义这个属性来指定不同的属性名来接收传入的属性值。
JSX.ElementChildrenAttribute
:用于决定 children 属性的名称。默认情况下,children 是一个特殊的属性,用于接收子元素。通过这个属性,可以自定义 children 的名称。
JSX.ElementChildrenAttribute
应该被声明在单一的属性(property)里。
declare namespace JSX {
interface ElementChildrenAttribute {
customChildrenName: JSX.Element[];
}
}
function MyComponent({ customChildrenName }: { customChildrenName: JSX.Element[] }) {
return <div>{customChildrenName}</div>;
}
const childElement = <p>Child Element</p>;
const jsxExpression = <MyComponent customChildrenName={[childElement]} />;
在这个示例中,我们自定义了 children 的名称为 customChildrenName
,并在 MyComponent
组件中接收这个属性。在使用 MyComponent
时,我们将一个子元素通过 customChildrenName
属性传递给组件。
通过这种方式,可以更加灵活地控制组件接收子元素的方式,并且通过类型检查确保子元素的类型正确。
子元素类型检查
单个子元素
如果一个组件只接收一个子元素,可以明确指定子元素的类型。
示例:
interface SingleChildComponentProps {
// child类型为 JSX.Element
child: JSX.Element;
}
//
function SingleChildComponent({ child }: SingleChildComponentProps) {
return <div>{child}</div>;
}
使用时:
const childElement = <p>Child Element</p>;
// 正确的使用
const element14 = <SingleChildComponent child={childElement} />;
// 错误的使用,会产生编译错误
const element15 = <SingleChildComponent child={123} />; // child 应该是 JSX.Element 类型
多个子元素
如果一个组件接收多个子元素,可以使用数组类型。
示例:
interface MultipleChildrenComponentProps {
// children 类型为 JSX.Element[]
children: JSX.Element[];
}
function MultipleChildrenComponent({ children }: MultipleChildrenComponentProps) {
return <div>{children}</div>;
}
使用时:
const childElements = [<p>Child 1</p>, <p>Child 2</p>];
// 正确的使用
const element16 = <MultipleChildrenComponent children={childElements} />;
// 错误的使用,会产生编译错误
const element17 = <MultipleChildrenComponent children={123} />; // children 应该是 JSX.Element 类型的数组
JSX结果类型
JSX表达式结果的类型 默认为any
。
可以通过指定JSX.Element
接口自定义JSX表达式结果的类型。
不能够从接口里检索元素,属性或JSX的子元素的类型信息。 它是一个黑盒。