TypeScript 类、泛型的使用实践记录|青训营

94 阅读7分钟

什么是泛型

泛型(Generics)是一种特性,它允许我们在定义函数、类或接口时使用类型参数,以在使用时指定具体的类型。泛型能够增加代码的灵活性,使代码可以在不同类型上工作,而不需要针对每个类型都编写重复的代码。通过使用泛型,可以实现类型安全性,并在编译时检查类型错误,避免运行时类型错误。

比较C++与TypeScript的类型参数化

  • C++ 和 TypeScript 都允许在函数、类或数据结构的定义中使用类型参数,这些类型参数在使用时可以被具体类型替代。这样一来,代码可以在多种类型上工作,而无需为每种类型编写重复的代码。
  • 类型安全性:C++ 和 TypeScript 都在编译时进行类型检查,这使得在使用泛型时能够捕获潜在的类型错误,提供更高的类型安全性。
  • 代码重用:通过泛型,C++ 和 TypeScript 都能够实现代码的重用,减少了重复编写类似代码的工作量,提高了代码的可维护性和可读性。
  • 泛型函数和泛型类:C++ 和 TypeScript 都支持泛型函数和泛型类的定义,使得函数或类能够适用于不同类型的参数或成员。

泛型的定义与应用场景

定义与使用

class Person {
  // 类的属性
  name: string;
  age: number;
  // 构造函数
  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }
  // 类的方法
  sayHello(): void {
    console.log(`Hello, my name is ${this.name} and I'm ${this.age} years old.`);
  }
}
const person1 = new Person("John", 30);
person1.sayHello(); // 输出:Hello, my name is John and I'm 30 years old.

应用场景
​ ​ ​ ​ ​ ​ ​ ​ 当我们写代码的时候,有时候会遇到很复杂的问题,尤其是在做大型应用或者复杂的功能时。这时候,TypeScript 类就像是一个强大的工具,可以帮助我们更好地组织代码,让它看起来更整齐,也更容易维护。
​ ​ ​ ​ ​ ​ ​ ​ 例如,我们想要要做一个网站,里面有很多用户需要注册和登录。我们可以用一个叫做“用户”的类,里面装着用户的信息,比如用户名、密码等。然后,我们就可以用这个类来处理用户的登录和注册,就像是把用户的信息放进一个方便的盒子里。
​ ​ ​ ​ ​ ​ ​ ​ 再举个例子,假设我们在做一个购物网站,用户可以往购物车里添加商品。我们可以用一个叫做“购物车”的类来管理购物车里的商品,把商品的信息都放在这个类里面。这样,我们可以很方便地添加商品、删除商品,还能快速计算出购物车里所有商品的总价。
​ ​ ​ ​ ​ ​ ​ ​ 如果我们要验证用户填写的表单是否完整,我们可以用一个叫做“表单验证器”的类来处理这个任务。我们只需要告诉它需要验证哪些字段,然后它就会自动帮我们检查是否有遗漏的信息。
​ ​ ​ ​ ​ ​ ​ ​ 总之,TypeScript 类就像是一个有用的工具,可以帮助我们写出更整洁、更易读的代码,让开发变得更高效。无论是开发网站还是手机应用,都可以用上这个强大的工具来提升编程的效率。

泛型函数

定义与使用

function identity<T>(arg: T): T {
  return arg;
}

let result2 = identity<string>("Hello"); // 指定类型参数为string
let result3 = identity<boolean>(true); // 指定类型参数为boolean

应用场景
泛型函数能帮助我们处理各种不同类型的数据问题。
​ ​ ​ ​ ​ ​ ​ ​ 假设你在写一个函数,要对数组里的元素进行加倍操作,但你可能会面对整数数组或者浮点数数组。这时候,就可以使用泛型函数来处理这个操作,让它既适用于整数数组,也适用于浮点数数组。就不需要写两个几乎一样的函数。
​ ​ ​ ​ ​ ​ ​ ​ 还有,开发者可能需要写一个查找函数,要从一个字符串数组中找到匹配的字符串。但你可能不只是要查找字符串,还想在数字数组里找某个数字。你可以使用泛型函数来实现查找操作,让你的查找功能更灵活,更通用。
​ ​ ​ ​ ​ ​ ​ ​ 泛型函数能够让代码更灵活、更智能地处理不同类型的数据。无论是处理数组、对象还是其他数据,泛型函数都能让你的编程更方便、更高效。

泛型类

定义和使用

class Box<T> {
  private value: T;
  constructor(value: T) {
    this.value = value;
  }
  getValue(): T {
    return this.value;
  }
}
let box1 = new Box<number>(10); // 指定类型参数为number
let box3 = new Box<boolean>(true); // 指定类型参数为boolean

应用场景
​ ​ ​ ​ ​ ​ ​ ​ 比方说,你可能在写一个容器类,要装各种不同类型的东西,比如水果、书籍、玩具等等。这样就可以用泛型类来定义这个容器,然后在使用时,可以往里面放任何类型的东西,不需要专门为每种类型写不同的容器类,省了很多时间。
​ ​ ​ ​ ​ ​ ​ ​ 再比如,你在写一个列表类,要存储各种数据,比如数字、字符串、日期等等。你可以用泛型类来实现这个列表,然后在使用时,可以存储任何类型的数据。

泛型接口

定义和使用

interface Pair<K, V> {
  key: K;
  value: V;
}
let pair1: Pair<number, string> = { key: 1, value: "One" };
let pair2: Pair<string, boolean> = { key: "isTrue", value: true };

应用场景
​ ​ ​ ​ ​ ​ ​ ​ 有时候要设计一个数据结构,以存储各种不同类型的数据。比如,我们可能需要一个列表,可以存储数字数组、字符串数组或者其他类型的数组。此时,可以用泛型接口来定义这个列表的接口,然后在使用时,指定它要存储的数据类型。这样,我们可以用同一个数据结构来存储不同类型的数据。

类型约束

​ ​ ​ ​ ​ ​ ​ ​ 在 TypeScript 中,类型约束是一种强大的特性,可以增加代码的灵活性和安全性。通过类型约束,我们可以对变量、函数参数、函数返回值以及类的属性和方法等进行限制,确保它们满足特定的类型条件。这样做可以避免一些潜在的类型错误,提高代码的可维护性和可读性。

function getAverage<T extends number>(arr: T[]): number {
  let sum: number = 0;
  for (let num of arr) {
    sum += num;
  }
  return sum / arr.length;
}// 使用泛型来实现一个函数,计算数组中元素的平均值
const numbers = [1, 2, 3, 4, 5];
const average = getAverage(numbers);
console.log(average); // 输出:3
interface NamedObject {
  name: string;// 使用类型约束来限制泛型只能是具有name属性的对象
}
function getName<T extends NamedObject>(obj: T): string {
  return obj.name;
}
const person = { name: "John", age: 30 };
const productName = getName(person);
console.log(productName); // 输出:John
// 错误示例,因为类型约束要求传入的对象必须有name属性
// const product = { title: "Laptop", price: 1000 };
// const productName = getName(product); // 编译错误

​ ​ ​ ​ ​ ​ ​ ​ 在上面的示例中,我们定义了两个使用类型约束的函数。第一个函数 getAverage 使用了 <T extends number>,表示泛型 T 必须是 number 类型或者其子类型,这样就确保了我们只能传入数字数组来计算平均值,而不能传入其他类型的数组。
​ ​ ​ ​ ​ ​ ​ ​ 第二个函数 getName 使用了 <T extends NamedObject>,表示泛型 T 必须是一个具有 name 属性的对象,这样就限制了我们只能传入带有 name 属性的对象,从而避免了在不具备 name 属性的对象上调用该函数导致的错误。
​ ​ ​ ​ ​ ​ ​ ​ 通过合理地使用类型约束,我们可以在编写泛型函数时增加代码的灵活性和安全性,避免不符合预期的类型错误,提高代码的可读性和可维护性。