前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >TypeScript系列教程六《泛型》

TypeScript系列教程六《泛型》

作者头像
星宇大前端
发布2021-07-19 15:33:18
9180
发布2021-07-19 15:33:18
举报
文章被收录于专栏:大宇笔记

软件工程中,我们不仅要创建一致的定义良好的API,同时也要考虑可重用性。 组件不仅能够支持当前的数据类型,同时也能支持未来的数据类型,这在创建大型系统时为你提供了十分灵活的功能。

在像C#和Java这样的语言中,可以使用泛型来创建可重用的组件,一个组件可以支持多种类型的数据。 这样用户就可以以自己的数据类型来使用组件。

初识泛型


1、我现在有一个盒子函数,想往里面装数字,我们可以这么写。

代码语言:javascript
复制
function box(...addPrams:number[]) {
    let box:Array<number> = []
    box = box.concat(addPrams)
    return box
}

console.log(box(1,2,3,4));

2、但是这个盒子不想只装number,还想装字符串,对象,集合,数组等等,当然我们可以写成any

代码语言:javascript
复制
function box(...addPrams:any[]) {
    let box:Array<any> = []
    box = box.concat(addPrams)
    return box
}

console.log(box(1,2,3,4));

但是这么写,我们就丢失了类型信息,返回的就是any[]

3、这时候泛型就出来了,使用泛型既能支持任意类型,还能保持类型系统的完备性

代码语言:javascript
复制
function box<T>(...addPrams:T[]) {
    let box:Array<T> = []
    box = box.concat(addPrams)
    return box
}

console.log(box("1","2"));

泛型变量


假如我想泛型传入数组,在泛型函数里面遍历数组,是不是type可以代表数组,可以使用数组的length属性?

测试:

代码语言:javascript
复制
function loggingIdentity<Type>(arg: Type): Type {
  console.log(arg.length);
   //Property 'length' does not exist on type 'Type'.
  return arg;
}

答案是否定的。

我们需要在泛型函数参数声明数组:

代码语言:javascript
复制
function loggingIdentity<Type>(arg: Array<Type>): Array<Type> {
  console.log(arg.length); // Array has a .length, so no more error
  return arg;
}

或者

代码语言:javascript
复制
function loggingIdentity<Type>(arg: Type[]): Type[] {
  console.log(arg.length);
  return arg;
}

泛型类型


泛型函数的类型与非泛型函数的类型没什么不同,只是有一个类型参数在最前面,像函数声明一样:

代码语言:javascript
复制
function identity<T>(arg: T): T {
    return arg;
}

let myIdentity: <T>(arg: T) => T = identity;

我们也可以使用不同的泛型参数名,只要在数量上和使用方式上能对应上就可以。

代码语言:javascript
复制
function identity<T>(arg: T): T {
    return arg;
}

let myIdentity: <U>(arg: U) => U = identity;

这引导我们去写第一个泛型接口了。 我们把上面例子里的对象字面量拿出来做为一个接口:

代码语言:javascript
复制
interface GenericIdentityFn {
    <T>(arg: T): T;
}

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

let myIdentity: GenericIdentityFn = identity;

一个相似的例子,我们可能想把泛型参数当作整个接口的一个参数。 这样我们就能清楚的知道使用的具体是哪个泛型类型(比如: Dictionary而不只是Dictionary)。 这样接口里的其它成员也能知道这个参数的类型了。

代码语言:javascript
复制
interface GenericIdentityFn<T> {
    (arg: T): T;
}

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

let myIdentity: GenericIdentityFn<number> = identity;

除了泛型接口,我们还可以创建泛型类。 注意,无法创建泛型枚举和泛型命名空间。

泛型类


泛型类看上去与泛型接口差不多。 泛型类使用( <>)括起泛型类型,跟在类名后面。

代码语言:javascript
复制
class GenericNumber<NumType> {
  zeroValue: NumType;
  add: (x: NumType, y: NumType) => NumType;
}

let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function (x, y) {
  return x + y;
};

GenericNumber类的使用是十分直观的,并且你可能已经注意到了,没有什么去限制它只能使用number类型。 也可以使用字符串或其它更复杂的类型。

代码语言:javascript
复制
let stringNumeric = new GenericNumber<string>();
stringNumeric.zeroValue = "";
stringNumeric.add = function(x, y) { return x + y; };

console.log(stringNumeric.add(stringNumeric.zeroValue, "test"));

与接口一样,直接把泛型类型放在类后面,可以帮助我们确认类的所有属性都在使用相同的类型。

我们在类那节说过,类有两部分:静态部分和实例部分。 泛型类指的是实例部分的类型,所以类的静态属性不能使用这个泛型类型。

泛型约束


你应该会记得之前的一个例子,我们有时候想操作某类型的一组值,并且我们知道这组值具有什么样的属性。 在 loggingIdentity例子中,我们想访问arg的length属性,但是编译器并不能证明每种类型都有length属性,所以就报错了。

代码语言:javascript
复制
function loggingIdentity<T>(arg: T): T {
    console.log(arg.length);  // Error: T doesn't have .length
    return arg;
}

相比于操作any所有类型,我们想要限制函数去处理任意带有.length属性的所有类型。 只要传入的类型有这个属性,我们就允许,就是说至少包含这一属性。 为此,我们需要列出对于T的约束要求。

为此,我们定义一个接口来描述约束条件。 创建一个包含 .length属性的接口,使用这个接口和extends关键字来实现约束:

代码语言:javascript
复制
interface Lengthwise {
    length: number;
}

function loggingIdentity<T extends Lengthwise>(arg: T): T {
    console.log(arg.length);  // Now we know it has a .length property, so no more error
    return arg;
}

现在这个泛型函数被定义了约束,因此它不再是适用于任意类型:

代码语言:javascript
复制
loggingIdentity(3);  // Error, number doesn't have a .length property

我们需要传入符合约束类型的值,必须包含必须的属性:

代码语言:javascript
复制
loggingIdentity({length: 10, value: 3});

在泛型约束里使用类型参数

您可以声明受另一个类型参数约束的类型参数。 例如,这里我们想从一个给定名称的对象中获取一个属性。 我们想确保我们不会意外地获取 obj 上不存在的属性,因此我们将在两种类型之间放置一个约束:

代码语言:javascript
复制
function getProperty<Type, Key extends keyof Type>(obj: Type, key: Key) {
  return obj[key];
}

let x = { a: 1, b: 2, c: 3, d: 4 };

getProperty(x, "a");
getProperty(x, "m");
//Argument of type '"m"' is not assignable to parameter of type '"a" | "b" | "c" | "d"'.

在泛型约束里使用类类型

在TypeScript使用泛型创建工厂函数时,需要引用构造函数的类类型。比如,

代码语言:javascript
复制
function create<T>(c: {new(): T; }): T {
    return new c();
}

一个更高级的例子,使用原型属性推断并约束构造函数与类实例的关系。

代码语言:javascript
复制
class BeeKeeper {
    hasMask: boolean;
}

class ZooKeeper {
    nametag: string;
}

class Animal {
    numLegs: number;
}

class Bee extends Animal {
    keeper: BeeKeeper;
}

class Lion extends Animal {
    keeper: ZooKeeper;
}

function createInstance<A extends Animal>(c: new () => A): A {
    return new c();
}

createInstance(Lion).keeper.nametag;  // typechecks!
createInstance(Bee).keeper.hasMask;   // typechecks!
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2021/07/15 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 初识泛型
  • 泛型变量
  • 泛型类型
  • 泛型类
  • 泛型约束
    • 在泛型约束里使用类型参数
      • 在泛型约束里使用类类型
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档