泛型
在像C#和Java这样的语言中,可以使用泛型来创建可重用的组件,一个组件可以支持多种类型的数据。 这样用户就可以以自己的数据类型来使用组件。
// T : 泛型变量 | 类型变量
function identity<T>(arg: T): T {
return arg;
}
identity(123)
identity('123')
代码中 T 代表 「Type」,在定义泛型时通常用作第一个类型变量名称。但实际上 T 可以用任何有效名称代替。
由于我们操作的是数组,所以.length属性是应该存在的。 我们可以像创建其它数组一样创建这个数组:
function loggingIdentity<T>(arg: T[]): T[] {
console.log(arg.length); // Array has a .length, so no more error
return arg;
}
其实并不是只能定义一个类型变量,我们可以引入希望定义的任何数量的类型变量。比如我们引入一个新的类型变量 U,用于扩展我们定义的 identity 函数:
function identity<T, U>(value: T, message: U): T {
console.log(message);
return value;
}
console.log(identity(666, "jack is cool!"));
泛型类型 && 泛型接口
泛型类型不同的注解方式:
- 函数泛型的注解方式
- 对象字面量的方式来定义泛型类型
- 泛型接口的定义方式
// 泛型类型的不同方式
function identity<T>(arg: T): T {
return arg;
}
// 1. 函数泛型的注解方式:
let a: <T>(arg: T) => T = identity
// 2. 对象字面量的方式来定义泛型类型
let b: { <T>(arg: T): T } = identity
// 3. 泛型接口的定义方式
interface IdentityInterface {
<T>(arg: T): T
}
let c: IdentityInterface = identity
泛型类 & 泛型约束
泛型类
泛型类看上去与泛型接口差不多,我们只需要在类名后面,使用 <T, ...> 的语法定义任意多个类型变量,具体示例如下:
// 泛型类
class MinClass<T>{
public list: T[] = []
add(num: T) {
this.list.push(num)
}
min(): T {
let minNum = this.list[0]
for (let i = 0; i < this.list.length; i++) {
if (minNum > this.list[i]) {
minNum = this.list[i]
}
}
return minNum
}
}
我们在什么时候需要使用泛型呢?通常在决定是否使用泛型时,我们有以下两个参考标准:
- 当你的函数、接口或类将处理多种数据类型时;
- 当函数、接口或类在多个地方使用该数据类型时。
泛型约束
有时我们可能希望限制每个类型变量接受的类型数量,这就是泛型约束的作用。
以官方文档例子为例:
我们需要去定义一个接口来描述约束条件。
创建一个包含 .length属性的接口,使用这个接口和extends关键字来实现约束:
interface LengthInterface {
length: number
}
function loggingIdentity<T extends LengthInterface>(arg: T): T {
console.log(arg.length);
return arg;
}
其中,泛型约束用的是: extends 继承接口的方式(不一定非要是接口) 、T extends LengthInterface 用于告诉编译器,我们支持已经实现 Length 接口的任何类型
另外, 泛型约束并不一定用接口方式, 比如 我们可以把以上代码 接口 换成 类型别名 , 如下例子: // 以类型别名的方式依然可以
type LengthType = string
function loggingIdentity<T extends LengthType>(arg: T): T {
console.log(arg.length);
return arg;
}
keyof 操作符
keyof操作符可以用于获取某种类型的所有键,其返回类型是联合类型。
// keyof 操作符
interface Person {
name: string;
age: number;
location: string;
}
type K1 = keyof Person; // "name" | "age" | "location"
type K2 = keyof Person[]; // number | "length" | "push" | "concat" | ...
type K3 = keyof { [x: string]: Person }; // string | number
可以看出, k1,k2,k3其实是键名
在泛型约束中使用类型参数
当我们理解了 keyof 操作符时, 看下面代码就很好理解了
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'.
**而 K 就是指 'a' , 'b' , 'c' , 'd' **
很明显通过使用泛型约束,在编译阶段我们就可以提前发现错误,大大提高了程序的健壮性和稳定性。
多重泛型约束 & 交叉类型
交叉类型是将多个类型合并为一个类型。 这让我们可以把现有的多种类型叠加到一起成为一种类型,它包含了所需的所有类型的特性。
function extend<T, U>(first: T, second: U): T & U {
let result = <T & U>{}; // 断言, 表示 result 包含 T U 俩种类型
for (let id in first) {
(<any>result)[id] = (<any>first)[id]; // 将 first 中所有属性 给 result
}
for (let id in second) {
if (!result.hasOwnProperty(id)) {
(<any>result)[id] = (<any>second)[id];
// 将 second 中所有属性 给 result, 前提是result 没有该属性时
}
}
return result; // 返回的 result 就有 first 和 second 的所有属性
}
class Person {
constructor(public name: string) { }
}
interface Loggable {
log(): void;
}
class ConsoleLogger implements Loggable {
log() {
// ...
}
}
var jim = extend(new Person("Jim"), new ConsoleLogger());
var n = jim.name;
jim.log();
再举个简单例子:
interface Sentence {
content: string
title: string
}
interface Music {
url: string
}
class Test<T extends Sentence & Music>{
props: T
constructor(public arg: T) {
this.props = arg
}
info() {
return {
// 这里可以 this.props.xxx 的原因是因为 arg 符合 T类型, 而T 又继承了上面俩个接口的属性。
content: this.props.content,
title: this.props.title,
url: this.props.url
}
}
}
泛型中的类类型
泛型中的类类型 目的是 约束或者更好的推论
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!