TypeScript JSX

介绍

在线的免费的代码转换器工具:可以把HTML 代码移植到 JSX 语法。还支持很多其他的转换。

JSX 是 ECMAScript 的一种类似 XML 的语法扩展,没有任何定义的语义。它不打算由引擎或浏览器实现。它并不是将 JSX 纳入 ECMAScript 规范本身的提议。它旨在供各种预处理器(转译器)使用这些标记转换为标准 ECMAScript。

JSX 是一种在 JavaScript 中编写类似 XML 结构来描述用户界面的语法。

TypeScript支持内嵌,类型检查以及将JSX直接编译为JavaScript。

基本用法

想要使用JSX必须做两件事:

  1. 给文件一个.tsx扩展名:
    • 向开发工具和编译器表明该文件中可能包含 JSX 语法。
  2. 启用jsx选项
    • 启用 jsx 选项通常是在 tsconfig.json 文件中进行配置。

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-classtitle 的值 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 元素)的检查主要包括以下几个方面:

  1. TypeScript 会检查使用的固有元素名称是否正确。
    • 必须使用正确的 HTML 元素名称,如 <div><p><span> 等。使用错误或不存在的元素名称会导致编译错误。
// 以下使用是合法的
<input type="text" placeholder="Enter text" />

// 以下使用会引发错误
<myInvalidElement />  // 不存在这样的固有元素
  1. 对于固有元素的属性,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 方法。

MyComponentMyFactoryFunction 是有效的,因为它们创建的对象都有 render 方法,符合 JSX.ElementClass 的要求。

NotAValidComponentNotAValidFactoryFunction 是无效的,因为它们创建的对象没有 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 属性,类型为布尔值。

固有元素的属性类型检查

  1. 确定属性类型的方式:
  • 对于基于值的元素(如类组件或无状态函数组件),属性类型的确定稍微复杂一些。它取决于先前确定的在元素实例类型上的某个属性的类型,而具体使用哪个属性来确定类型取决于 JSX.ElementAttributesProperty 的定义。
  • 如果未指定 JSX.ElementAttributesProperty,在 TypeScript 2.8 及以后版本中,将使用类元素构造函数或无状态函数组件调用的第一个参数的类型。
declare namespace JSX {
  interface ElementAttributesProperty {
    props; // 指定用来使用的属性名
  }
}

class MyComponent {
  props: {
    foo?: string;
  };
}

// `MyComponent`的元素属性类型为`{foo?: string}`
<MyComponent foo="bar" />;
  1. 支持可选属性和必须属性:
    元素属性类型可以定义可选属性和必须属性。
    示例:
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 属性,类型为数字。

额外属性和泛型类型

  1. JSX.IntrinsicAttributes 接口:
    • JSX.IntrinsicAttributes 接口可以用来指定额外的属性,这些属性通常不会被组件的 propsarguments 使用。例如在 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 中明确声明。

  1. 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 可以用来描述可能传递给组件的通用属性,这些属性不是由组件明确定义的属性,而是类似 styleclassNamekey 等在 JSX 中经常使用的属性。

示例:

interface MyComponentProps {
  customProp: string;
}

function MyComponent({
  customProp
}: MyComponentProps & JSX.IntrinsicAttributes) {
  return (
    <div className={/*...*/} style={/*...*/}>
      {customProp}
    </div>
  );
}

在示例中,MyComponent 组件除了接收自定义的 customProp 属性外,还可以接收 JSX.IntrinsicAttributes 中定义的那些通用属性,如 classNamestyle
如果不使用 JSX.IntrinsicAttributes,当尝试在使用 MyComponent 时传递 classNamestyle 等属性时,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的子元素的类型信息。 它是一个黑盒。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值