前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >泛型_TypeScript笔记6

泛型_TypeScript笔记6

作者头像
ayqy贾杰
发布2019-06-12 15:09:04
1K0
发布2019-06-12 15:09:04
举报
文章被收录于专栏:黯羽轻扬黯羽轻扬

一.存在意义

考虑这样一个场景,identity函数接受一个参数,并原样返回:

function identity(arg) {
 return arg;
}

从类型上看,无论参数是什么类型,返回值的类型都与参数一致,借助重载机制,可以这样描述:

function identity(arg: number): number;
function identity(arg: string): string;
function identity(arg: boolean): boolean;
// ...等无数个 a => a 的类型描述

重载似乎并不能满足这个场景,因为我们没有办法穷举arg的所有可能类型。既然参数是任意类型,不妨用any试试:

function identity(arg: any): any;

覆盖到了所有类型,却丢失了参数与返回值的类型对应关系(上面相当于A => B的类型映射,而我们想要描述的是A => A

泛型与any

那么,应该如何表达两个any之间的对应关系呢?

用泛型。这样描述:

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

类型变量Tany类似,相当于具名any,这样就能明确表达T => T(即A => A)的类型映射了

二.类型变量

Type variable, a special kind of variable that works on types rather than values.

普通变量代表一个值,而类型变量代表一个类型

从作用上看,变量能够搬运值,而类型变量搬运的是类型信息:

This allows us to traffic that type information in one side of the function and out the other.

三.泛型函数

类型变量也叫类型参数,与函数参数类似,区别在于函数参数接受一个具体值,而类型参数接受一个具体类型,例如:

function identity<T>(arg: T): T {
 return arg;
}// 传参给类型参数
// identity<number>
// 传参给函数参数(自动推断类型参数)
identity(1);
// 传参给函数参数(显式传入类型参数)
identity<number>(1);

带有类型参数的函数称为泛型函数,其中类型参数代表任意类型(any and all types),所以只有所有类型共有的特征才能访问:

function loggingIdentity<T>(arg: T): T {
 // 报错 Property 'length' does not exist on type 'T'.
 console.log(arg.length);
 return arg;
}

实际上,因为有void这个空集在,所以并不存在所有类型通用的属性或方法。也不能对类型变量做任何假设(比如假定它有length属性),因为它代表一个任意类型,没有任何约束

除此之外,类型变量T就像一个具体类型一样,可以用于任何具体类型出没的地方:

function loggingIdentity<T>(arg: T[]): T[] {
 console.log(arg.length);  // Array has a .length, so no more error
 return arg;
}
// 或者
function loggingIdentity<T>(arg: Array<T>): Array<T> {
 console.log(arg.length);  // Array has a .length, so no more error
 return arg;
}

类型描述

泛型函数的类型描述与普通函数类似:

// 普通函数
let myIdentity: (arg: string) => string =
 function(arg: string): string {
   return arg;
 };
// 泛型函数
let myIdentity: <T>(arg: T) => T =
 function<T>(arg: T): T {
   return arg;
 };

仍然是箭头函数语法,只是在(参数列表)前增加了<类型参数列表>。同样的,类型描述中类型参数名也可以与实际的不一致:

let myIdentity: <U>(arg: U) => U =
 function<T>(arg: T): T {
   return arg;
 };

P.S.特殊的,函数类型描述还可以写成对象字面量的形式:

// 泛型函数
let myIdentity: { <T>(arg: T): T };
// 普通函数
let myIdentity: { (arg: string): string };

像是接口形式类型描述的退化版本,没有复用优势,也不如箭头函数简洁,因此,并不常见

四.泛型接口

带类型参数的接口叫泛型接口,例如可以用接口来描述一个泛型函数:

interface GenericIdentityFn {
 <T>(arg: T): T;
}

还有一种非常相像的形式:

interface GenericIdentityFn<T> {
 (arg: T): T;
}

这两种都叫泛型接口,区别在于后者的类型参数T作用于整个接口,例如:

interface GenericIdentity<T> {
 id(arg: T): T;
 idArray(...args: T[]): T[];
}
let id: GenericIdentity<string> = {
 id: (s: string) => s,
 // 报错 Types of parameters 's' and 'args' are incompatible.
 idArray: (...s: number[]) => s,
};

接口级的类型参数有这种约束作用,成员级的则没有(仅作用于该泛型成员)

五.泛型类

同样,带类型参数的类叫泛型类,例如:

class GenericNumber<T> {
 zeroValue: T;
 add: (x: T, y: T) => T;
}

像接口一样,泛型类能够约束该类所有成员关注的目标类型一致:

Putting the type parameter on the class itself lets us make sure all of the properties of the class are working with the same type.

注意,类型参数仅适用于类中的实例成员,静态成员无法使用类型参数,例如:

class GenericNumber<T> {
 // 报错 Static members cannot reference class type parameters.
 static zeroValue: T;
}

因为静态成员在类实例间共享,无法唯一确定类型参数的具体类型:

let n1: GenericNumber<string>;
// 期望 n1.constructor.zeroValue 是 string
let n2: GenericNumber<number>;
// 期望 n1.constructor.zeroValue 是 number,出现矛盾

P.S.这一点与Java一致,具体见Static method in a generic class?

六.泛型约束

类型参数太“泛”(any and all)了,在一些场景下,可能想要加以约束,例如:

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;
}

通过接口来描述对类型参数的约束T extends constraintInterface),比如上面要求类型参数T必须具有一个number类型的length属性`

另一个典型的场景是工厂方法,例如:

// 要求构造函数c必须返回同一类(或子类)的实例
function create<T>(c: {new(): T; }): T {
 return new c();
}

此外,还可以在泛型约束中使用类型参数,例如:

function getProperty<T, K extends keyof T>(obj: T, key: K) {
 return obj[key];
}let x = { a: 1, b: 2, c: 3, d: 4 };getProperty(x, "a"); // okay
getProperty(x, "m"); // error: Argument of type 'm' isn't assignable to 'a' | 'b' | 'c' | 'd'.

能够用一个类型参数的特征去约束另一个类型参数,相当强大

七.总结

之所以叫泛型,是因为能够作用于一系列类型,是在具体类型之上的一层抽象:

Generics are able to create a component that can work over a variety of types rather than a single one. This allows users to consume these components and use their own types.

参考资料

  • Generics
  • 类型参数 | 类型_Haskell笔记3
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-02-09,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 前端向后 微信公众号,前往查看

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一.存在意义
    • 泛型与any
    • 二.类型变量
    • 三.泛型函数
      • 类型描述
      • 四.泛型接口
      • 五.泛型类
      • 六.泛型约束
      • 七.总结
        • 参考资料
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档