TypeScript 类、泛型的使用实践记录
这是我参与「第六届青训营 」伴学笔记创作活动的第 7 天
引言
TypeScript 是 JavaScript 的超集,它扩展了 JavaScript 的功能,使其更适合大型项目的开发。在 TypeScript 中,类和泛型是两个重要的概念。类可以帮助我们组织代码,并将数据和相关的行为封装在一起;而泛型则可以使我们的代码更通用和灵活。本文将通过实例和解释,详细展示 TypeScript 中类和泛型的使用。
为什么要学习 TS
现在的趋势
TS 的活跃度日益增高,在工程里的应用比较广泛。
TS 和 JS 的对比
TS 由微软团队开发,是 JS 的一个超集,在 JS 的基础上去做一些类型检测和相关的语法补充,本质上为 JS 增加了静态类型和一些面向对象编程的相关能力(或者说是后端语言的相关特性)。
TS 带来了什么
- 类型安全,类似 Java、C++
- 包含了一些编译器,支持 JS 里面 ES 的新特性,功能类似于Bable 之类的处理
- 因为 TS 带来了类型,我们可以从 IDE 上更容易去理解代码,可以围绕着这些编码属性来整理一些相关的工具,来提高我们的开发效率
所以说,TS 不是一门语言,而是一种开发工具
Typescript 学习推荐
Awesome Typescript:Github 上整理的的 TS 开源教程及应用
ByteTech:TS&React:React + Typescript 开发模式的介绍
TypeScript Playground:TS到JS在线编译
类 - class
Typescript 类的介绍和定义
在 TypeScript 中,类是一种用于创建对象的蓝图或模板。它定义了对象的属性和方法,以及其行为和状态的集合。类在面向对象编程中起到了重要的作用,它具有以下特点:
- 封装性:类可以将属性和方法封装在一起,以形成一个对象,并隐藏内部实现细节。这样可以防止外部对对象的直接访问和篡改,增加了数据的安全性。
- 继承性:类可以通过继承关系扩展现有的类,并可以重用父类的属性和方法。子类可以添加新的属性和方法,以满足特定的需求。
- 多态性:类可以通过继承和接口来实现多态。多态允许子类以自己特定的方式重写父类的方法或属性,从而实现不同的行为。
TS 支持 ES6 引入的类的关键字,在此基础上补充了相关的语法糖,提高了效率和阅读性。TS 和 JS 的类的定义有一些不同。
定义:写法和JS差不多,增加了一些定义
在 TypeScript 中,我们可以通过关键字 class 来定义一个类。以下是一个简单的示例:
class Person {
name: string; // 属性
constructor(name: string) { // 构造函数
this.name = name;
}
sayHello() { // 方法
console.log(`Hello, my name is ${this.name}.`);
}
}
let person = new Person("Alice"); // 创建一个 Person 对象
person.sayHello(); // 调用对象的方法
在上面的代码中,我们定义了一个名为 Person 的类。它有一个属性 name 和一个构造函数 constructor,构造函数用于初始化类的实例。Person 类还有一个方法 sayHello(),用于打印一个问候语。我们可以通过 new 关键字实例化一个 Person 对象,并调用其方法。
通过类的定义,我们可以创建具有共享属性和行为的对象,并将其用于代码的组织和复用。
特点:超级
- 增加了 public、private、protected 修饰符
看下这个 Demo:
首先,默认的方法和属性都是默认 public 的
然后,private 是私有属性,private 定义之后,继承类和实例都不能去调用这个私有属性
最后,protected 是受保护的,仅支持再继承类中被调用,生成的一些实例是不能调用的
- 抽象类:
- 只能被继承,不能被实例化
- 作为基类,抽象方法必须被子类实现
抽象类的相关 Demo:
我们无法对 Animal 进行实例化,只能对它进行继承,然后对他的子类进行实例化的操作过程。
要注意的一点是,在 TS 中,井号标识是表示这是一个私有属性,外界是无法进行调用的。
- interface 约束类,使用 implements 关键字
如果要使用 interface 来约束类的话,可以使用 implements 关键字,来进行接口方面的定义,方便开发者来判断当前的类是否满足接口相关的定义。
TypeScript类的简单使用实践
在TypeScript中,类是一种封装了属性和方法的数据结构。它可以帮助我们组织和管理代码,并提供了面向对象编程的特性。下面是一个使用TypeScript类的示例:
class Person {
private name: string;
private age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
public introduce(): void {
console.log(`My name is ${this.name} and I'm ${this.age} years old.`);
}
}
const person = new Person("John", 25);
person.introduce(); // 输出:My name is John and I'm 25 years old.
在上面的示例中,定义了一个名为Person的类。它有两个私有属性name和age,并通过构造函数接收这两个属性的值。类还有一个公共方法introduce,用于打印自我介绍的信息。在类的实例化过程中,传入了名为John和25的参数,并调用了introduce方法,打印了John的自我介绍。
这个示例展示了TypeScript类的基本结构和使用方法。可以继续通过添加更多属性和方法来扩展这个类,从而实现更复杂的功能。
泛型
在 Typescript 高级类型中,分为需要泛型和不需要泛型的时候,下面我们来讲讲涉及到泛型和什么时候需要泛型
泛型 - 什么时候需要泛型
官方定义:
软件工程中,我们不仅要创建一致的的定义良好的 API,同时也要考虑到可重用性。
组件不仅能够支持当前的数据类型,同时也能支持未来的数据类型,这在创建大型系统时为你提供了十分灵活的功能。
在像 C# 和 Java 这样的语言中,可以使用泛型来创建可重用的组件。
一个组件可以支持多种类型的数据,这样用户就可以以自己的数据类型来使用组件。
TypeScript 泛型介绍
TypeScript 的泛型是一种在编程中用于增强代码灵活性和重用性的特性。通过使用泛型,可以在定义函数、类或接口时不指定具体类型,而是使用占位符(类型参数)。在使用时,可以根据实际情况传入不同的具体类型,从而实现对不同类型的支持和复用。
泛型的优点有以下几个方面:
- 代码重用:使用泛型可以编写通用的代码,可以轻松地适应多种类型的数据,减少了重复代码的编写。
- 类型安全:通过使用泛型,可以在编译时捕获类型错误,并提供更好的类型检查和推断。
- 抽象数据类型的支持:泛型可以用于创建抽象数据类型,例如集合类(List、Stack)或容器类,使其更加通用。
下面是一些常见的使用泛型的情况:
- 泛型函数:定义一个函数时,可以使用泛型来处理输入参数或返回值的类型。例如,
function identity<T>(arg: T): T { ... },这个函数可以接受任意类型的参数,并返回相同类型的值。 - 泛型类:类可以使用泛型来定义属性、方法或构造函数的参数。例如,
class ArrayList<T> { ... },这个类可以创建一个可操作各种类型的数组。 - 泛型接口:接口也可以使用泛型来定义属性、方法或函数的类型。例如,
interface List<T> { ... },这个接口可以定义一个通用的列表结构。
需要注意的是,TypeScript 的泛型只在编译时起作用,运行时会被擦除,因为 JavaScript 是一种动态类型的语言。因此,泛型在编译阶段会进行类型检查,但在真正执行时,并不会对泛型进行运行时类型检查。
- 应用场景:定义一个print函数,这个函数的功能是把传入的参数打印出来,再返回这个参数,传入参数的类型是string,函数返回类型是string.1、想支持打印number类型?2、想支持打印数据类型?任意类型? 思考:需要有一个类型解决输入输出可关联的问题
function print(arg:string):string{
console.log(arg)
return arg
}
function print(arg:string | number):string | number{
console.log(arg)
return arg
}
function print(arg:any):any{
console.log(arg)
return arg
}
function print<T>(arg:T):T{
console.log(arg)
return arg
}
- 泛型的语法是<>里面写类型参数,一般用T表示;
- 使用时有两种方法指定类型:1、定义要使用的类型;2、通过TS类型推断,自动推导类型
- (泛型的核心)泛型的作用是临时占位,之后通过传来的类型进行推导
function print<T>(arg:T):T{
console.log(arg)
return arg
}
print<string>('hello') // 定义T为string
print('hello') // TS类型推断,自动推导类型为string
- 泛型工具类型-基础操作符:
- typeof:获取类型
- keyof:获取所有键
- in:遍历枚举类型
- T:索引访问
- extends:泛型约束
TypeScript泛型的简单使用实践
在TypeScript中,泛型允许在定义函数、类或接口时延迟指定具体类型,以实现代码的复用性和灵活性。下面是一个使用TypeScript泛型的示例:
function reverse<T>(items: T[]): T[] {
return items.reverse();
}
const numbers = [1, 2, 3, 4, 5];
const reversedNumbers = reverse(numbers);
console.log(reversedNumbers); // 输出:[5, 4, 3, 2, 1]
const strings = ["hello", "world"];
const reversedStrings = reverse(strings);
console.log(reversedStrings); // 输出:["world", "hello"]
在上面的示例中,定义了一个名为reverse的函数。它接收一个类型为T的数组作为参数,并返回一个反转后的数组。通过使用泛型类型参数T,可以将函数应用于不同类型的数组,从而实现了代码的复用性。
这个示例展示了TypeScript泛型的灵活性和复用性。可以在函数、类和接口中使用泛型,以适应不同类型的数据。
TS工程化实例
TS实战
- declare:三方库需要类型声明文件
- .d.ts:声明文件定义
- @types:三方库TS类型包
- tsconfig.json:定义TS的配置
案例
以下代码为什么会提示错误,应该如何解决?
type User = {
id:number;
kind:string;
}
function makeCustomer<T extends User>(u: T): T{
// Error
// Type '{id: number; kind: string}' is not assignable to type 'T'
// '{id: number; kind: string;}' is assignable to the constraint of type 'T'
// but 'T' could be instaniated with a different subtype of constraint 'User'
return {
id: u.id;
kind: 'customer
}
}
出错原因:泛型T只是约束于User类型,并不是局限于User类型,所以返回结果应该还需要接收其他类型变量。
- 解决办法一:T类型兼容User类型
function makeCustomer<T extends User>(u: T): T{
return {
...u,
id: u.id,
kind:'customer'
}
- 解决方法二:返回值类型修改为User类型
function makeCustomer<T extends User>(u: T): User{
return {
id: u.id,
kind: 'customer',
}
}
function makeCustomer<T extends User>(u: T): ReturnMake<T, User>{
return {
id: u.id,
kind: 'customer',
}
}
type ReturnMake<T extends User, U> = {
[k in keyof U as K extends keyof T ? K: never]: U[K]
}
makeCustomer({id:18923323, kind: '888', price: 99})
// 1、ReturnMake工具类型,接收T、U两个泛型,T约束于User
// 2、遍历User中的Key,并使用as断言,如果K(也就是User类型的key),约束于泛型类型的key返回k,否则返回never,U[K]取键值