# xlt-ts-demo **Repository Path**: wei_pengcheng/xlt-ts-demo ## Basic Information - **Project Name**: xlt-ts-demo - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2024-07-10 - **Last Updated**: 2024-07-11 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README ## TypeScript基本概念 ### 安装编译TS的工具包 安装命令 ``` npm i -g typescript 或 yarn global add typscript ``` ### 编译并运行TS代码 编译ts代码使用命令: ``` tsc 文件名.ts ``` 编译成功后,会在同级目录出现一个同名的js文件。 ## Typescript基础 ### 类型注解 - TS是JS的超集,TS提供了js的所有功能,并且额外增加了类型系统 - 所有的js代码都是ts代码 - js有类型,但是js不会检查变量的类型是否发生了变化,而ts会检查。 - TS类型系统的主要优势:可以显示标记处代码中的意外行为,从而降低了发生错误的可能性。 示例代码 ```ts let age =18 let age:number =18 ``` - 说明:代码中的`:number`是类型注解 - 作用:为变量添加类型约束, - 结束:约定了什么类型,就只能个变量赋值该类型的值,否则就会报错 ### Ts类型概述 TS中常用基础类型细分为两类: - JS已有类型 - 原始类型:number、string、boolean、null、undefined - 复杂数据类型:array、object、function - TS新增类型 - 联合类型 - 自定义类型(类型别名) - 接口 - 元组 - 字面量类型 - 枚举 - void ### TS原始数据类型 TypeScript 的原始数据类型包括: 1. **number**: 用于表示整数或浮点数。 2. **string**: 用于表示文本。 3. **boolean**: 表示逻辑值 `true` 或 `false`。 4. **null**: 特殊关键字,表示没有值。 5. **undefined**: 表示未定义的值。 ### 数组类型 在 TypeScript 中,数组类型可以用两种方式来定义: 1. **使用元素类型后跟 `[]`**: - 这种方式是最简单的数组类型声明方式,直接在元素类型后面加上 `[]` 来表示这是一个该类型元素的数组。 - 例如,`number[]` 表示一个由数字组成的数组。 2. **使用泛型数组类型 `Array<元素类型>`: - 这种方式使用泛型语法 `Array<元素类型>` 来定义数组,其中 `元素类型` 是数组中每个元素的类型。 - 例如,`Array` 表示一个由数字组成的数组。 两种方式在功能上是等价的,您可以根据个人偏好选择使用。数组类型允许您在数组中存储同一类型的多个值,并且可以使用数组的方法来操作这些值,例如 `push`、`pop`、`slice` 等。在 TypeScript 中使用数组类型,可以帮助您更好地进行类型检查和代码自动补全,提高开发效率和代码质量。 ### 联合类型 在 TypeScript 中,联合类型(Union Type)是一种高级类型,它允许一个值可以是几种类型之一。使用联合类型可以增加代码的灵活性和可用性。联合类型使用竖线 `|` 分隔每个类型。 ```ts let myFavoriteNumber: string | number myFavoriteNumber = 'seven' myFavoriteNumber = 7 let myFavoriteArray: (string | number) [] = [1, 2, 3,'4'] ``` - 联合类型(Union Types)表示取值可以为多种类型中的一种。 - 只能访问联合类型的所有类型里共有的属性或方法 - `|` 表示或 它的优先级较低,因此使用` | `时,通常会用括号来括起来,避免优先级错误 **使用场景** 联合类型特别适用于以下场景: - 函数需要接受多种类型的参数。 - 一个变量可能赋值为多种类型。 - 在处理第三方库的对象时,对象属性可能是多种类型之一。 ### 类型别名 在 TypeScript 中,类型别名(Type Aliases)允许你给一个类型起一个新的名字。类型别名常用于给一个可能比较复杂的类型一个更简单的名字,使代码更易于理解和维护。类型别名使用 `type` 关键字定义。 #### **基本用法** ```ts type StringOrNumber = string | number; let value: StringOrNumber; value = "Hello"; // 正确 value = 100; // 正确 value = false; // 错误,TypeScript 会报错 ``` 在这个例子中,`StringOrNumber` 是一个类型别名,它代表 `string` 或 `number` 类型。 **结构类型** 类型别名也可以用于定义对象结构。 ```ts type User = { name: string; age: number; }; const user: User = { name: "Alice", age: 30 }; ``` 在这个例子中,`User` 是一个类型别名,用于描述一个具有 `name` 和 `age` 属性的对象。 #### **联合类型和交叉类型** 类型别名可以用于联合类型(Union Types)和交叉类型(Intersection Types)。 ```ts type StringOrNumber = string | number; type Loggable = { log: () => void; }; type UserWithLog = User & Loggable; function logUser(user: UserWithLog) { user.log(); } ``` 在这个例子中,`UserWithLog` 是一个交叉类型,表示一个对象既满足 `User` 类型,也满足 `Loggable` 类型。 #### **使用场景** 类型别名特别适用于以下场景: - 当你想给一个可能很复杂的类型一个更简单的名字时。 - 当你想创建联合类型或交叉类型时。 - 当你想在多个地方重用同一个类型,但不想每次都写出完整的类型定义时。 类型别名是 TypeScript 提供的一个强大特性,它可以让你的类型定义更加清晰和易于管理。 ### 函数类型 函数类型在 TypeScript 中是用来确保函数的参数类型和返回值类型符合预期的一种方式。通过明确地指定这些类型,可以提高代码的可读性和健壯性。以下是关于函数类型的几个关键点的总结: #### 基本定义 函数类型的基本形式是通过参数类型和返回值类型来定义的,格式如下: ```ts (参数: 类型, 参数: 类型, ...) => 返回值类型 ``` #### 使用接口定义函数类型 可以使用接口来定义一个函数类型,这样做可以让代码更加清晰和模块化。 ```ts interface SearchFunc { (source: string, subString: string): boolean; } ``` #### 可选参数和默认参数 - **可选参数**:通过在参数名后加 `?` 来标记该参数为可选。 - 注意:必选参数不能位于可选参数后 ```ts function buildName1(firstName: string, lastName?: string): string { ... } ``` - **默认参数**:可以为参数提供一个默认值,如果调用时未传入该参数,则使用默认值。 ```ts function buildName2(firstName: string, lastName: string = 'Cat'): string { ... } ``` #### 剩余参数 使用 `...` 操作符定义剩余参数,允许将一个不确定数量的参数作为一个数组传入。 ```ts function buildName3(firstName: string, ...restOfName: string[]): string { ... } ``` #### 函数重载 函数重载允许一个函数接受不同数量或类型的参数时,作出不同的处理。 ```ts function reverse(x: number): number; function reverse(x: string): string; ``` 通过这些特性,TypeScript 的函数类型提供了强大的类型检查和灵活性,使得函数的使用和实现更加安全和清晰。 ### 对象类型 在 TypeScript 中,对象类型允许你定义一个对象应该包含哪些属性以及每个属性的类型。这是确保对象满足特定结构的一种方式,非常有助于在编译时期捕获错误。以下是关于对象类型的几个关键点的总结: #### 基本定义 对象类型可以通过在花括号内指定属性名和属性类型来定义,格式如下: ```ts { property1: type1; property2: type2; ... } ``` #### 可选属性 通过在属性名后添加 `?` 来标记该属性为可选,表示该属性在对象中可以存在也可以不存在。 ```ts { requiredProperty: string; optionalProperty?: number; } ``` #### 只读属性 通过在属性名前添加 `readonly` 关键字来标记该属性为只读,表示该属性一旦被赋值后就不能再被修改。 ```ts { readonly immutableProperty: string; } ``` #### 索引签名 如果对象的所有属性都遵循相同的类型,但是你不知道属性的具体名称,可以使用索引签名。 ```ts { [key: string]: any; } ``` #### 使用接口定义对象类型 接口(`interface`)是定义对象类型的另一种方式,它提供了更强大的语法来描述对象的结构。 ```ts interface Person { name: string; age?: number; // 可选属性 readonly gender: string; // 只读属性 } ``` #### 使用类型别名定义对象类型 类型别名(`type`)也可以用来定义对象类型,它与接口非常相似,但有一些细微的差别。 ```ts type Animal = { species: string; age?: number; }; ``` 对象类型是 TypeScript 中定义和使用对象的基础,它提供了强大的类型检查功能,帮助开发者编写更安全、更可维护的代码。 ### 接口类型 在 TypeScript 中,接口(Interfaces)是一种强大的方式来定义对象的结构。接口可以用来描述一个对象需要有哪些属性和方法,以及这些属性和方法的类型。以下是关于接口类型的几个关键点的总结: #### 基本定义 接口通过 `interface` 关键字定义,可以指定对象具有哪些属性以及属性的类型。 ```ts interface 接口名 {属性名: 属性类型, ...} ``` ```ts interface Person { name: string; age: number; } ``` #### 可选属性 接口中的属性可以被标记为可选的,使用 `?` 表示。这意味着实现接口的对象可以没有这个属性。 ```ts 属性名?: 属性类型 ``` ```ts interface Person { name: string; age?: number; // 可选属性 } ``` #### 只读属性 接口可以包含只读属性,这些属性在对象第一次赋值后就不能再被修改了。使用 `readonly` 关键字来标记。 ```ts readonly 属性名: 属性类型 ``` ``` interface Person { readonly id: number; name: string; } ``` #### 索引签名 接口可以使用索引签名来描述那些通过索引得到的类型,允许你动态地处理属性。 ```ts [index: string]: string ``` ```ts interface StringDictionary { [index: string]: string; } ``` #### 实现接口 一个类可以实现一个接口,这意味着这个类必须包含接口中定义的所有属性和方法。 ```ts interface Greetable { greet(): void; } class Person implements Greetable { greet() { console.log("Hello!"); } } ``` #### 接口继承 接口可以继承一个或多个其他接口,这使得你可以从其他接口复制成员。 ```ts interface 接口名 extends 父接口名 {属性名: 属性类型, ...} // 多继承 interface 接口名 extends 父接口名1, 父接口名2, ... {属性名: 属性类型, ...} // 混合类型 interface 接口名 {属性名: 属性类型, 方法名: () => void, ...} // 7. 接口继承类 interface 接口名 extends 类名 {属性名: 属性类型, ...} ``` ```ts interface Named { name: string; } interface Greetable extends Named { greet(): void; } ``` 接口是 TypeScript 中定义和使用对象的结构的一种非常灵活和强大的方式,它们提供了一种清晰的方法来定义契约,类或对象必须遵守这些契约。 #### interface vs type 在 TypeScript 中,`interface` 和 `type` 都可以用来定义对象的形状或者函数签名,但它们在某些方面有着不同的用途和限制。理解这些差异对于有效地使用 TypeScript 来说是很重要的。 **相同点** 1. **定义形状**:都可以描述一个对象或函数的形状。 2. **扩展**:都支持扩展(`interface` 使用 `extends`,`type` 使用交叉类型 `&`)。 **不同点** 1. **声明合并**: - **`interface`**:支持自动合并多个同名接口声明。 - **`type`**:不支持声明合并。如果有多个同名的类型别名,会报错。 2. **扩展方式**: - **`interface`**:通过 `extends` 关键字扩展其他接口。 - **`type`**:可以通过交叉类型(`&`)扩展其他类型,包括接口和其他类型别名。 3. **使用场景**: - **`interface`**:更适合定义公共 API 的形状,因为它更易于扩展和实现。 - **`type`**:更适合定义联合类型或元组类型,以及当你需要使用到类型的交叉时。 4. **复杂类型**: - **`type`**:可以用来定义联合类型、元组类型等更复杂的类型。 5. **实现和继承**: - **`interface`**:类可以实现接口(`implements`),接口可以继承自其他接口。 - **`type`**:虽然不能直接通过 `implements` 实现一个类型别名,但如果类型别名描述了一个对象字面量的形状,类可以满足这个形状。 **选择使用哪一个?** - 如果你正在定义一个库或者公共 API 的类型,或者需要其他人扩展你的类型,使用 `interface`。 - 如果你需要定义联合类型、元组类型或者你需要使用到类型的交叉,使用 `type`。 - 在其他情况下,它们大多数时候是可以互换的,选择哪一个更多地取决于个人或团队的偏好。 ### 元组类型 在 TypeScript 中,元组(Tuple)类型允许你表达一个数组,其中元素的数量和类型是已知的,但不必全部相同。这对于定义具有固定数量元素的数组非常有用,且每个元素可以是不同的类型。以下是关于元组类型的一些关键点: #### 基本使用 - **定义元组**:通过指定每个元素的类型来定义元组。 ```ts let userInfo: [string, number] = ['Alice', 30]; // 定义了一个包含 string 和 number 类型元素的元组 ``` #### 访问元素 - **通过索引访问**:可以通过索引来访问元组中的元素,索引从0开始。 ```ts let name: string = userInfo[0]; // 访问第一个元素 let age: number = userInfo[1]; // 访问第二个元素 ``` #### 元组的操作 - **添加元素**:可以向元组中添加新的元素,但添加的元素类型必须是元组中已定义类型之一。 ```ts userInfo.push('female'); // 错误,如果元组定义时没有包含 string 类型的元素 ``` #### 可选元素 - **使用 `?` 表示可选元素**:元组中的元素可以是可选的,表示该元素可能不存在。 ```ts let user: [string, number, boolean?] = ['Bob', 25]; ``` #### 剩余元素 - **使用 `...` 操作符**:可以使用剩余元素(rest elements)来表示元组中的最后几个元素属于同一类型。 ```ts let numbers: [string, ...number[]] = ['numbers', 1, 2, 3]; ``` #### 元组的应用 - **函数参数和返回值**:元组非常适合用于定义函数的参数列表和返回值类型,特别是当函数需要返回多个值时。 ```ts function useState(initialState: S): [S, (newState: S) => void] { let state = initialState; let setState = (newState: S) => { state = newState; }; return [state, setState]; } ``` #### 剩余元素 在 TypeScript 中,你可以使用剩余元素(rest elements)语法来定义一个具有剩余元素的元组类型。剩余元素语法允许你表示一个元组的最后几个元素属于同一类型,但数量不定。这通过在元素类型前使用三个点(`...`)来实现。 以下是定义具有剩余元素的元组类型的示例: ```ts type StringNumberBooleans = [string, number, ...boolean[]]; ``` 在这个例子中,`StringNumberBooleans` 类型表示一个元组,其中: - 第一个元素必须是 `string` 类型。 - 第二个元素必须是 `number` 类型。 - 从第三个元素开始,可以有零个或多个 `boolean` 类型的元素。 使用这种类型定义元组时,你可以像这样创建变量: ```ts let example: StringNumberBooleans = ['hello', 42, true, false, true]; ``` 这里,`example` 是一个符合 `StringNumberBooleans` 类型的元组,它以一个 `string` 和一个 `number` 开始,后面跟着任意数量的 `boolean` 值。 元组类型是 TypeScript 中一种非常有用的工具,它提供了一种方法来组织和处理具有不同类型的固定数量的数据项。 ### 类型推论 在 TypeScript 中,类型推论(Type Inference)是指编译器自动确定表达式类型的能力。这意味着在很多情况下,你不需要显式指定变量的类型,因为 TypeScript 能够根据上下文自动推断出类型。这使得代码更简洁,同时仍然保持类型安全。以下是类型推论的一些关键点: #### 基本类型推论 当你初始化变量时,TypeScript 会根据赋值的数据类型推断变量的类型。 ```ts let name = "Alice"; // 推断为 string let age = 30; // 推断为 number let isStudent = true; // 推断为 boolean ``` #### 函数返回类型推论 TypeScript 会根据函数中的返回语句推断函数的返回类型。 ```ts function getLength(s) { return s.length; // 返回类型推断为 number } ``` #### 最佳通用类型推论 当需要从多个表达式推断类型时,TypeScript 会尝试找到一个最佳通用类型来兼容所有候选类型。 ```ts let arr = [0, 1, null]; // 推断为 (number | null)[] ``` #### 上下文类型推论 在某些情况下,TypeScript 会根据左侧的类型来推断右侧表达式的类型,这称为上下文类型。 ```ts window.onmousedown = function(mouseEvent) { console.log(mouseEvent.button); // 推断 mouseEvent 为 MouseEvent 类型 }; ``` #### 限制 尽管类型推论提供了便利,但在复杂的情况下可能不会按照你的预期工作。在这些情况下,显式类型注解是必要的,以确保类型安全。 #### 使用建议 - 对于简单的变量和表达式,充分利用类型推论简化代码。 - 在公共 API 的参数和返回类型上使用显式类型注解,以提高代码的可读性和维护性。 - 当编译器无法准确推断类型或推断的类型不符合预期时,提供显式类型。 类型推论是 TypeScript 提供的一个强大特性,它能够让你的代码更加简洁,同时保持类型的准确性和安全性。 ### 字面量类型 在 TypeScript 中,字面量类型是一种特殊的类型,它允许你将变量限定为一个具体的值。当你希望变量不仅仅是一个类型,而是一个确切的值时,字面量类型非常有用。字面量类型可以是字符串、数字或布尔值的字面量。 #### 字符串字面量类型 ```ts let direction: "up" | "down" | "left" | "right"; direction = "up"; // 正确 direction = "down"; // 正确 // direction = "anywhere"; // 错误:类型 '"anywhere"' 不能赋值给类型 '"up" | "down" | "left" | "right"' ``` #### 数字字面量类型 ```ts let statusCode: 200 | 404 | 500; statusCode = 200; // 正确 statusCode = 404; // 正确 // statusCode = 300; // 错误:类型 '300' 不能赋值给类型 '200 | 404 | 500' ``` #### 布尔字面量类型 布尔字面量类型较少见,因为布尔类型本身只有两个值 `true` 和 `false`,但在某些特定场景下可能会用到。 ```ts let isTrue: true; isTrue = true; // 正确 // isTrue = false; // 错误:类型 'false' 不能赋值给类型 'true' ``` #### 应用场景 字面量类型在定义一些特定值集合的场景下非常有用,例如配置选项、状态码、方向控制等。它们可以与联合类型、类型别名和接口结合使用,提供强大的类型安全保证。 #### 联合字面量类型 联合字面量类型允许变量从一组指定的字面量值中选择一个值。 ```ts type Response = "success" | "failure" | "pending"; let fetchStatus: Response; fetchStatus = "success"; // 正确 // fetchStatus = "loading"; // 错误:类型 '"loading"' 不能赋值给类型 'Response' ``` #### 总结 字面量类型通过限制变量到一个具体的值,提供了一种强大的方式来实现类型安全。它们在需要精确控制值的场景下非常有用,可以帮助避免错误和提高代码的可读性。 ### 枚举类型 在 TypeScript 中,枚举(Enum)类型是一种特殊的类型,它允许你为一组数值赋予友好的名字。枚举类型是对 JavaScript 标准数据类型的一个补充。使用枚举可以清晰地表达意图或创建一组有区别的用例。TypeScript 支持数字和字符串的枚举。 #### 语法 ```ts enum 枚举名 { 标识符[=整数], 标识符[=整数], ... } ``` #### 数字枚举 ```ts enum StatusCode { Success = 200, NotFound = 404, ServerError = 500 } let response = StatusCode.Success; console.log(response); // 输出 200 ``` 数字枚举在没有初始化时会被赋予从0开始的递增值。 #### 字符串枚举 ```ts enum Direction { Up = "UP", Down = "DOWN", Left = "LEFT", Right = "RIGHT" } let move = Direction.Up; console.log(move); // 输出 "UP" ``` 字符串枚举允许你使用字符串字面量而不是数字。这可以提供更好的可读性。 #### 常量枚举 常量枚举通过在枚举前使用 `const` 关键字定义。它们在编译阶段会被删除,并且不能包含计算成员。 ```ts const enum Month { Jan, Feb, Mar } let month = Month.Jan; console.log(month); // 输出 0 ``` #### 异构枚举 异构枚举包含字符串和数字成员。虽然 TypeScript 允许这样做,但不推荐使用异构枚举,因为它们可以引起混淆。 ```ts enum BooleanLikeHeterogeneousEnum { No = 0, Yes = "YES", } ``` #### 枚举成员的特性 - **只读**:枚举成员在被赋值后是只读的,不能被修改。 - **反向映射**:数字枚举支持由枚举值到枚举名字的反向映射。 #### 使用场景 枚举适用于管理一组固定的相关值,如状态码、方向、月份等。它们提高了代码的可读性和可维护性。 #### 总结 枚举类型是 TypeScript 中一个非常有用的特性,它提供了一种有效的方式来组织有关联的值集合。通过使用枚举,你可以使你的代码更加清晰和易于理解。 ### any类型 在 TypeScript 中,`any` 类型是一个强大的方式,允许你在编译时关闭 TypeScript 的类型检查。使用 `any` 类型的变量,可以被赋予任何类型的值,这使得 `any` 类型非常灵活,但同时也会失去 TypeScript 提供的大部分类型安全保障。 #### 使用 `any` 类型 ```ts let notSure: any = 4; notSure = "maybe a string instead"; notSure = false; // okay, definitely a boolean ``` 在这个例子中,变量 `notSure` 被声明为 `any` 类型,这意味着你可以给它赋予任何类型的值,而 TypeScript 编译器不会报错。 #### `any` 类型的影响 虽然 `any` 类型提供了极大的灵活性,但它也意味着放弃了类型检查。这可能会导致以下问题: - **类型安全**:使用 `any` 类型,你将无法享受 TypeScript 的类型检查带来的好处。这可能会导致运行时错误。 - **自动补全**:在使用 `any` 类型的变量时,IDE 不会提供自动补全和方法提示,因为编译器不知道变量的确切类型。 - **代码可读性**:过度使用 `any` 类型会使代码难以理解,其他开发者(或未来的你)可能会难以推断变量的类型。 #### 适当使用 `any` 类型 尽管 `any` 类型有其缺点,但在某些情况下使用 `any` 类型是合理的,例如: - 当你正在从 JavaScript 迁移到 TypeScript,且暂时无法确定某些变量的类型时。 - 当使用第三方库,而该库的类型定义不清晰或不准确时。 - 在进行一些复杂的操作,且确实无法预先知道变量类型的情况下。 #### 总结 `any` 类型是 TypeScript 中一个非常灵活的类型,它允许变量接受任何类型的值。然而,过度使用 `any` 类型会失去 TypeScript 提供的类型安全和其他优势。因此,建议仅在确实需要时使用 `any` 类型,并尽量使用更具体的类型或 TypeScript 的其他高级类型特性,如泛型(`Generics`)和类型断言,来保持代码的健壮性和可维护性。 ### 类型断言 类型断言在 TypeScript 中是一种强制类型转换的方式,但它不进行实际的数据转换。它只是在编译时期告诉编译器,你已经对变量的类型有了明确的了解,并且希望将其视为另一种类型。类型断言不会改变变量的实际类型,在运行时不会有任何影响。 #### 使用类型断言的两种语法: 1. **尖括号语法**: ```ts let someValue: any = "这是一个字符串"; let strLength: number = (someValue).length; ``` 1. **`as` 语法**: ```ts let someValue: any = "这是一个字符串"; let strLength: number = (someValue as string).length; ``` 在 JSX 中,只能使用 `as` 语法,因为尖括号语法与 JSX 的元素语法冲突。 #### 类型断言的使用场景: - 当你比 TypeScript 更了解某个值的详细信息时,例如,当你从一个普通的 JavaScript 对象中获取一个特定的属性,但你确信该属性存在。 - 在处理联合类型时,你可能需要明确指出变量的具体类型。 - 当你与 DOM 元素交互时,通常需要断言具体的元素类型以访问特定的属性或方法。 #### 注意事项: - 类型断言不是类型转换,它不会改变实际的数据类型。 - 使用类型断言时应当谨慎,确保你对变量的类型非常清楚。 - 过度使用类型断言可能会隐藏真正的类型问题,减少类型系统的保护作用。 #### 总结: 类型断言是 TypeScript 中的一个重要概念,它提供了一种方式来告诉编译器你对变量的类型有明确的了解。正确使用类型断言可以使你的代码更加灵活和强大,但需要谨慎使用,以避免引入潜在的类型安全问题。 ## Typescript泛型 ### 泛型-基本介绍 泛型是 TypeScript 提供的一种工具,它允许在定义函数、接口或类时不预先指定具体的类型,而是在使用时再指定类型的一种特性。泛型提供了一种方法来确保函数或类的灵活性和复用性,同时保持类型安全。 #### 函数中使用泛型 ```ts function identity(arg: T): T { return arg; } ``` 这里,`` 用于定义一个泛型 `T`,你可以在函数的参数类型和返回类型中使用它。当你调用这个函数时,可以指定 `T` 是任何类型,这样就可以保证参数类型和返回类型是相同的。 #### 接口中使用泛型 ```ts interface GenericIdentityFn { (arg: T): T; } function identity(arg: T): T { return arg; } let myIdentity: GenericIdentityFn = identity; ``` 这个例子中,我们定义了一个泛型接口 `GenericIdentityFn`,它包含一个方法签名,这个方法接受一个类型为 `T` 的参数并返回一个类型为 `T` 的值。然后我们可以将这个接口用于函数,指定具体的类型。 #### 类中使用泛型 ```ts class GenericNumber { zeroValue: T; add: (x: T, y: T) => T; } let myGenericNumber = new GenericNumber(); myGenericNumber.zeroValue = 0; myGenericNumber.add = function(x, y) { return x + y; }; ``` 在类中使用泛型可以让类在实例化时指定特定的类型。在这个例子中,`GenericNumber` 类有一个属性 `zeroValue` 和一个方法 `add`,它们的类型都是泛型 `T`。当创建 `myGenericNumber` 实例时,我们指定 `T` 为 `number` 类型。 #### 泛型的好处 - **类型安全**:泛型提供了一种方式来确保函数的参数类型、返回类型以及类的属性和方法参数类型在使用时是一致的。 - **复用性**:通过泛型,你可以写出更通用的函数、接口和类,提高代码的复用性。 - **灵活性**:泛型让你的函数、接口和类可以支持多种类型,而不是单一的类型。 #### 总结 泛型是 TypeScript 中一个非常强大的特性,它提供了一种保持类型安全的同时增加代码复用性和灵活性的方法。通过使用泛型,你可以编写出既通用又类型安全的代码。 ### 泛型-泛型函数 泛型函数是 TypeScript 中一种允许函数接受不特定类型参数的功能,这使得函数可以更加通用和灵活。通过使用泛型,你可以创建能够工作于多种数据类型的函数,而不是限制于一个单一类型。这样不仅增加了代码的复用性,还保持了类型的安全性。 #### 泛型函数的基本语法 ```ts function identity(arg: T): T { return arg; } ``` 在这个例子中,`identity` 函数是一个泛型函数,`` 表示一个泛型类型。这个函数接受一个类型为 `T` 的参数,并返回一个类型为 `T` 的值。当你调用这个函数时,你可以指定 `T` 是任何类型,这样就可以保证参数类型和返回类型是相同的。 #### 调用泛型函数 你可以以两种方式之一调用泛型函数: 1. 明确地传入类型参数: ```ts let output = identity("myString"); ``` 1. 利用类型推断(更常见): ```ts let output = identity("myString"); ``` 在第二种方式中,TypeScript 编译器会查看传递给 `identity` 函数的参数,并根据传入的值自动确定 `T` 的类型。这种方式简化了代码,同时保持了类型安全。 #### 泛型函数的优势 - **类型安全**:泛型函数在编译时会进行类型检查,确保函数的参数类型和返回类型相匹配,从而提供类型安全。 - **复用性**:通过泛型,你可以创建一个逻辑相同但可以操作不同类型数据的函数,提高代码复用性。 - **灵活性**:泛型函数可以接受多种类型的参数,使得函数更加灵活,能够用于更广泛的场景。 #### 总结 泛型函数是 TypeScript 中一个非常强大的特性,它通过允许函数操作多种类型的数据,提高了函数的通用性、复用性和灵活性,同时保持了类型的安全性。通过合理使用泛型,你可以编写出既通用又类型安全的高质量代码。 ### 简化泛型函数调用 简化泛型函数调用的关键在于利用 TypeScript 的类型推断能力,避免在每次调用时显式指定类型参数。这样不仅可以使代码更简洁,还能提高代码的可读性和维护性。 ### 泛型函数调用简化步骤 1. **依赖类型推断**:当 TypeScript 能够根据传入的参数自动推断出类型参数时,就无需显式指定泛型类型。 ```ts function identity(arg: T): T { return arg; } // 使用类型推断而非显式指定 let output = identity("myString"); ``` 2. **使用类型约束**:通过给泛型添加约束,可以让 TypeScript 更好地推断类型,从而减少需要显式指定的场合。 ```ts function loggingIdentity(arg: T): T { console.log(arg.length); // 现在我们知道它具有`.length`属性 return arg; } // 调用时,TypeScript 会根据传入的参数推断出 T 的类型 let myArray = loggingIdentity(["item1", "item2"]); ``` 3. **利用上下文类型**:在某些情况下,TypeScript 可以根据上下文环境推断出泛型的类型,这时也可以省略类型参数。 ```ts interface Result { id: number; data: T; } // 函数返回类型的上下文可以帮助推断出 T 的类型 function getResult(id: number): Result { // 实现细节 return null; // 示例代码,实际应有具体实现 } // 上下文类型推断 let result: Result<{ name: string }> = getResult(1); ``` #### 总结 通过充分利用 TypeScript 的类型推断,可以在很多情况下省略泛型函数调用时的类型参数,使代码更加简洁和易于理解。这不仅减少了编码工作量,还提高了代码的通用性和灵活性。 ### 泛型约束 在 TypeScript 中,泛型约束允许你定义一个接口,以确保泛型类型具有特定的结构或属性。这是通过使用 `extends` 关键字来实现的,它可以限制泛型类型必须符合给定的接口或类型。 #### 泛型约束的基本语法 ```ts interface Lengthwise { length: number; } // 使用泛型约束确保 T 具有 length 属性 function loggingIdentity(arg: T): T { console.log(arg.length); // 现在我们知道 arg 必须有 length 属性 return arg; } ``` #### 使用泛型约束的步骤 1. **定义约束接口**:首先,定义一个接口或类型,它将作为你的约束条件。在上面的例子中,`Lengthwise` 接口要求任何符合约束的类型必须有一个 `length` 属性。 2. **应用约束到泛型**:在定义泛型函数或类时,使用 `extends` 关键字后跟你的约束接口或类型,来应用约束。这告诉 TypeScript,泛型类型 `T` 必须符合 `Lengthwise` 接口。 3. **使用泛型**:现在,当你使用这个泛型函数时,传入的参数必须满足 `Lengthwise` 接口的约束。如果传入的参数不满足约束,TypeScript 将会报错。 #### 示例:使用泛型约束 ```ts function loggingIdentity(arg: T): T { console.log(arg.length); return arg; } loggingIdentity({length: 10, value: 3}); // 正确 loggingIdentity(3); // 错误,数字没有 length 属性 ``` 在这个例子中,`loggingIdentity` 函数要求其参数 `arg` 必须符合 `Lengthwise` 接口。因此,当你尝试传入一个没有 `length` 属性的值时,TypeScript 编译器将会报错,从而确保了类型安全。 #### 总结 泛型约束是 TypeScript 中一个强大的特性,它允许你确保泛型类型符合特定的接口或结构。通过使用泛型约束,你可以编写更加灵活和安全的泛型函数、类或接口。 ### 指定更加具体的类型 ### 添加约束 在 TypeScript 中,添加约束通常是通过泛型约束来实现的。泛型约束允许你定义一个泛型函数或泛型类时,限制泛型类型必须满足某些条件。以下是如何在提供的代码片段中添加一个简单的泛型约束的步骤: #### 步骤 1. **定义一个接口**:首先,定义一个接口来描述约束条件。例如,如果你想要约束一个泛型类型必须有一个 `length` 属性,你可以创建一个包含 `length` 属性的接口。 2. **应用泛型约束**:使用 `extends` 关键字将接口应用为泛型类型的约束。这样,只有满足接口约束的类型才能被用作该泛型类型。 #### 示例代码 假设我们要添加一个函数,该函数接受一个参数,并且我们想要约束这个参数必须具有`length` 属性。我们可以这样做: ```ts // 1. 定义一个接口,描述约束条件 interface HasLength { length: number; } // 2. 创建一个函数,应用泛型约束 function logLength(arg: T): T { console.log(arg.length); // 现在可以安全地访问 length 属性 return arg; } // 测试函数 logLength("hello world"); // 正确,字符串有 length 属性 logLength([1, 2, 3]); // 正确,数组有 length 属性 // logLength(10); // 错误,数字没有 length 属性 ``` 在这个示例中,`HasLength` 接口定义了一个约束条件,即任何使用这个泛型的类型必须有一个 `length` 属性。然后,`logLength` 函数通过 `` 应用了这个约束,这意味着传递给 `logLength` 的参数必须满足 `HasLength` 接口的约束。这样,我们就可以在函数内部安全地访问 `arg.length`,同时保持函数的灵活性和重用性。 ### 多个类型变量 在 TypeScript 中,你可以使用泛型来创建可以工作于多种类型的组件。当你需要处理多个类型时,可以定义多个类型变量。这在创建高度可复用的组件时非常有用,比如函数、接口或类,它们可以同时操作多种数据类型。以下是如何定义和使用多个类型变量的步骤: #### 步骤 1. **定义多个类型变量**:在泛型的尖括号(`< >`)内,通过逗号分隔来定义多个类型变量。 2. **使用类型变量**:在你的函数、接口或类中,使用这些类型变量来指定参数类型、返回类型或成员变量的类型。 3. **调用时指定类型**:在使用泛型的地方,提供具体的类型来替换泛型中定义的类型变量。 #### 示例代码 假设我们要创建一个函数,该函数接受两个参数,这两个参数可以是不同的类型,然后返回一个包含这两个参数的数组。 ```ts // 定义一个泛型函数,使用两个类型变量 function pair(first: T, second: U): [T, U] { return [first, second]; } // 使用函数时指定类型 let result = pair("hello", 42); console.log(result); // 输出: ["hello", 42] // 也可以让 TypeScript 自动推断类型 let result2 = pair("world", 100); console.log(result2); // 输出: ["world", 100] ``` 在这个示例中,`pair` 函数定义了两个类型变量 `T` 和 `U`,它们分别代表第一个和第二个参数的类型。这允许你创建一个灵活的函数,可以接受任意类型的参数,并返回一个包含这两种类型的元组。当你调用 `pair` 函数时,可以显式地指定这些类型变量的类型,或者让 TypeScript 根据传递给函数的参数自动推断它们的类型。这种方法提高了代码的复用性和灵活性,同时保持了类型安全。 ### 泛型接口 在 TypeScript 中,泛型接口允许你定义一个接口,这个接口可以适用于多种类型。通过使用泛型,你可以在接口中定义一些属性或方法,这些属性或方法可以操作不同类型的数据,而不需要为每种数据类型都定义一个接口。 #### 定义泛型接口 泛型接口的定义方式与普通接口相似,但是在接口名后面添加了 ``(或者你可以使用任何其他有效的类型变量名),这表示这是一个泛型接口。 #### 示例 假设我们想要定义一个泛型接口 `IStorage`,这个接口包含两个方法:`get` 和 `set`,这两个方法分别用于获取和设置存储中的数据。数据的类型是泛型,这意味着我们可以使用 `IStorage` 接口来创建可以操作不同类型数据的存储。 ```ts // 定义泛型接口 interface IStorage { get(key: string): T; set(key: string, value: T): void; } // 实现泛型接口 class LocalStorage implements IStorage { private storage: Record = {}; get(key: string): T { return this.storage[key]; } set(key: string, value: T): void { this.storage[key] = value; } } // 使用泛型接口 const stringStorage = new LocalStorage(); stringStorage.set("name", "GitHub Copilot"); console.log(stringStorage.get("name")); // 输出: GitHub Copilot const numberStorage = new LocalStorage(); numberStorage.set("age", 30); console.log(numberStorage.get("age")); // 输出: 30 ``` 在这个示例中,`IStorage` 是一个泛型接口,它定义了两个方法:`get` 和 `set`。`LocalStorage` 类实现了这个接口,并使用一个对象来存储键值对。由于 `IStorage` 是泛型的,我们可以创建处理不同类型数据的 `LocalStorage` 实例,比如字符串和数字。 这种方式提供了很大的灵活性,允许你在不牺牲类型安全的情况下,重用接口和类。通过泛型,你可以编写更通用、可复用的代码。 ### JS中的泛型接口 ## Typescript类型声明文件 ### 基本介绍 TypeScript 类型声明文件(通常以 `.d.ts` 结尾)用于为现有的 JavaScript 代码提供类型信息。这使得 TypeScript 代码能够在不改变原有 JavaScript 代码的情况下,利用 TypeScript 的类型检查和自动完成等特性。类型声明文件对于在 TypeScript 项目中使用纯 JavaScript 库尤其重要。 #### 创建类型声明文件的步骤 1. **创建 `.d.ts` 文件**:在你的项目中,为需要声明类型的 JavaScript 库或模块创建一个 `.d.ts` 文件。文件名通常与 JavaScript 文件相对应,例如,如果你有一个 `utils.js` 文件,你应该创建一个 `utils.d.ts` 文件。 2. **声明模块**:使用 `declare module` 关键字声明模块。这告诉 TypeScript,模块存在,即使它是用 JavaScript 编写的。 3. **添加类型声明**:在模块声明内部,添加函数、变量、类等的类型声明。这些声明不包含实现细节,只描述公共 API 的类型信息。 4. **使用类型声明**:在 TypeScript 文件中引用 JavaScript 文件时,TypeScript 编译器会自动查找并使用相应的 `.d.ts` 文件来获取类型信息。 #### 示例 假设有一个简单的 JavaScript 工具库 `utils.js`,包含一个 `add` 函数: ```ts // utils.js function add(a, b) { return a + b; } ``` 为了在 TypeScript 中使用这个函数并获得类型检查,你可以创建一个 `utils.d.ts` 文件: ```ts // utils.d.ts declare module 'utils' { export function add(a: number, b: number): number; } ``` 然后,在 TypeScript 文件中,你可以这样使用 `add` 函数: ```ts import { add } from 'utils'; const sum = add(1, 2); console.log(sum); // 输出: 3 ``` 在这个例子中,`utils.d.ts` 文件为 `utils.js` 中的 `add` 函数提供了类型声明。这使得在 TypeScript 文件中导入并使用 `add` 函数时,能够享受到类型检查和代码自动完成等特性。 #### 注意事项 - 类型声明文件只提供类型信息,不包含实际的实现代码。 - 对于许多流行的 JavaScript 库,你可以在 DefinitelyTyped 项目中找到现成的类型声明文件。 - 使用 `npm install @types/库名` 命令可以安装这些库的类型声明,前提是它们已经在 DefinitelyTyped 上可用。 ### 内置类型声明文件 TypeScript 提供了一系列内置类型声明文件,用于描述 JavaScript 中的全局变量、函数、对象等的类型信息。这些声明文件通常以 `.d.ts` 结尾,包含在 TypeScript 的安装包中。它们为开发者提供了 JavaScript 标准库(如 `Array`, `Promise`, `Set`, `Map` 等)以及浏览器环境(如 `document`, `window`)和 Node.js 环境(如 `require`, `module`)的类型定义。 #### 主要内置类型声明文件 1. **lib.d.ts**:包含了 ECMAScript 标准库的类型声明,如 `String`, `Number`, `Array` 等。 2. **dom.d.ts**:包含了浏览器 DOM API 的类型声明,如 `document`, `HTMLElement`, `Event` 等。 3. **webworker.importscripts.d.ts**:为 Web Workers 提供类型声明。 4. **scripthost.d.ts**:提供了脚本宿主(如 Windows Script Host)的类型声明。 5. **es5.d.ts, es6.d.ts, es2016.d.ts, es2017.d.ts** 等:包含了对应 ECMAScript 版本的类型声明。 #### 使用内置类型声明文件 在 TypeScript 项目中,默认情况下,TypeScript 编译器会根据 `tsconfig.json` 文件中的 `lib` 选项自动包含相应的内置类型声明文件。如果没有指定 `lib` 选项,编译器会根据 `target` 选项自动选择合适的 ECMAScript 版本的类型声明文件。 例如,如果你的 `tsconfig.json` 中设置了 `"target": "es5"`,那么 TypeScript 会自动包含 `lib.d.ts` 和 `es5.d.ts`。如果你需要使用更高版本的 ECMAScript 特性,或者需要使用 DOM API,你可以在`tsconfig.json`的 `compilerOptions` 中手动指定 `lib` 选项: ```ts { "compilerOptions": { "target": "es5", "lib": ["es2015", "dom"] } } ``` 这会告诉 TypeScript 编译器,除了基本的 ECMAScript 5 类型声明之外,还应该包含 ECMAScript 2015(即 ES6)和 DOM API 的类型声明。 内置类型声明文件极大地简化了 TypeScript 项目的配置,使开发者能够轻松地使用 JavaScript 标准库和 Web API,同时享受到类型检查和代码自动完成等优势。 ### 第三方库类型声明文件 在 TypeScript 中,当使用第三方 JavaScript 库时,通常需要类型声明文件(`.d.ts` 文件),以便 TypeScript 能够理解库中的类型。这些类型声明文件为库中的函数、对象等提供了类型注解,使得 TypeScript 能够提供类型检查和代码自动完成等功能。 #### 获取第三方库的类型声明文件 1. **使用 @types** 大多数流行的 JavaScript 库的类型声明文件可以在 DefinitelyTyped 项目中找到。你可以通过 npm 或 yarn 安装对应的 `@types` 包来获取这些类型声明。例如,如果你在项目中使用了 Vue.js,可以通过以下命令安装 Vue 的类型声明文件: ```shell npm install @types/vue --save-dev //或者,如果你使用 yarn: yarn add @types/vue --dev ``` 2. **直接从库中获取** 一些库会直接在其 npm 包中包含类型声明文件。这意味着你不需要单独安装任何 `@types` 包。例如,Vue 3 已经直接包含了类型声明文件,所以当你通过 npm 或 yarn 安装 Vue 3 时,你会自动获得这些类型声明。 3. **手动编写或修改类型声明文件** 如果对于某个库不存在类型声明文件,或者现有的类型声明文件不满足你的需求,你可以手动编写或修改类型声明文件。创建一个 `.d.ts` 文件,并在其中添加你的类型声明。然后,确保这个文件被 TypeScript 编译器识别,通常是将其放在项目的根目录下或在 `tsconfig.json` 文件中指定的路径下。 ### 自定义类型声明文件-共享数据 要创建一个自定义类型声明文件以共享数据类型,你可以遵循以下步骤: 1. **定义数据类型**:首先,确定你需要共享的数据类型。例如,假设你有一个用户数据对象,包含姓名、年龄和电子邮件。 2. **创建类型声明文件**:在项目中创建一个新的 `.d.ts` 文件。通常,这个文件放在项目的根目录下或者一个专门的目录(如 `types`)中。 3. **编写类型声明**:在 `.d.ts` 文件中,使用 TypeScript 的类型系统来定义你的数据类型。 4. **在项目中使用类型**:确保 TypeScript 能够找到并使用你的类型声明文件。 #### 示例 假设你想共享一个用户对象的类型,你可以按照以下步骤操作: ##### 步骤 1 & 2: 创建类型声明文件 在你的项目中创建一个名为 `sharedTypes.d.ts` 的文件。你可以将它放在项目的根目录下,或者创建一个名为 `types` 的目录并将文件放在那里。 ##### 步骤 3: 编写类型声明 在 `sharedTypes.d.ts` 文件中,定义一个用户对象的类型: ```ts // sharedTypes.d.ts interface User { name: string; age: number; email: string; } ``` ##### 步骤 4: 在项目中使用类型 在任何需要使用 `User` 类型的 TypeScript 文件中,你可以直接引用它,因为 `.d.ts` 文件会被 TypeScript 自动识别。例如,在一个名为 `userUtils.ts` 的文件中,你可以这样使用 `User` 类型: ```ts // userUtils.ts function createUser(name: string, age: number, email: string): User { return { name, age, email }; } ``` 确保你的 `tsconfig.json` 配置文件包含了 `.d.ts` 文件的路径,这样 TypeScript 编译器就能找到并使用这些类型声明。如果你的类型声明文件放在了特定的目录下,比如 `types`,你可能需要在`tsconfig.json` 中添加如下配置: ```ts { "compilerOptions": { "typeRoots": ["./types", "./node_modules/@types"] } } ``` 这样,你就创建了一个自定义的类型声明文件,用于在项目中共享 `User` 数据类型。