泛型
是指宽泛的类型,通常用于类和函数,表示在使用的时候再指明类型,泛型就是解决类接口方法的复用性、以及对不特定数据类型的支持。泛型不仅可以让我们的代码变得更加健壮,还能让我们的代码在变得健壮的同时保持灵活性和可重用性。用法是通过用
来表示,放在参数的前面,T帮助我们捕获用户传入的类型。
function abc(value: number): number { return value; } //输入和输出都是number类型
function abc1(value: any): any { return value; } //输入和输出都是any类型,这样就丢失了一些信息:传入的类型与返回的类型应该是相同的
//给abc2添加了类型变量T,之后就可以使用这个类型,然后再次使用T当做返回值类型
function abc2<T>(value: T): T { return value; } //这就是泛型,可以适用于多个类型,同时输入的参数类型和返回值的类型是相同的
使用泛型创建像以上类似的泛型函数时,编译器要求我们在函数体内必须正确的使用这个通用的类型。 换句话说,我们必须把这些参数当做是任意或所有类型。例如以下示例:
function ab<T>(arg: T): T {
console.log(arg.length); //报错,因为T是任意或所有类型,而不是所有类型(如number和boolean)都有length属性
return arg;
}
//因此,我们可以进行些许更改:
function ab1<T>(arg: T[]): T[] { //泛型函数接收一个元素类型为T的数组
console.log(arg.length); //数组有属性length
return arg;
}
这可以让我们把泛型变量T当做类型的一部分使用,而不是整个类型,增加了灵活性。
泛型函数的类型与非泛型函数的类型没什么不同,只是有一个类型参数在最前面,像函数声明一样:
function identity<T>(arg: T): T {
return arg;
}
let myIdentity: <T>(arg: T) => T = identity;
//还可以使用带有调用签名的对象字面量来定义泛型函数
function identity1<T>(arg: T): T {
return arg;
}
let myIdentity: {<T>(arg: T): T} = identity1;
将泛型与接口结合起来使用,可以大大简化我们的代码,增加我们的代码可读性。
//泛型接口的不同写法一
interface as {
<T>(val: T): T
}
function ts<T>(val: T): T {
return val;
}
let myVal:as = ts;
console.log(myVal('阿巴阿巴'));
//泛型接口的不同写法二:把泛型参数当作整个接口的一个参数,这样我们就能清楚的知道使用的具体是哪个泛型类型,接口里的其它成员也能知道这个参数的类型
interface as1<T> {
(val: T): T
}
function ts1<T>(val: T): T {
return val;
}
let myVal1:as1<string> = ts1;
console.log(myVal1('鞠婧祎'));
泛型类看上去与泛型接口差不多。 泛型类使用 <>
括起泛型类型,跟在类名后面。与接口一样,直接把泛型类型放在类后面,可以帮助我们确认类的所有属性都在使用相同的类型。
class List<T> {
public list: T[] = [];
add(a: T): void {
this.list.push(a);
}
}
let s = new List<string>();
s.list = ['小红','小美','小兰'];
s.add('111');
console.log(s); //List { list: [ '小红', '小美', '小兰', '111' ] }
console.log(s.list); //[ '小红', '小美', '小兰', '111' ]
class Info<T1,T2> {
username: T1;
pwd: T2;
constructor(username: T1,pwd: T2) {
this.username = username;
this.pwd = pwd;
}
}
let info = new Info<string,string>('八月八','123456');
console.log(info); //Info { username: '八月八', pwd: '123456' }
注意:泛型类指的是实例部分的类型,所以类的静态属性不能使用这个泛型类型!
在TypeScript中,我们需要严格的设置各种类型,在使用泛型之后将会变得更加灵活,但同时也将会存在一些问题。因此我们需要对泛型进行约束来解决这些问题。
function ab<T>(arg: T): T {
console.log(arg.length); //报错,因为T是任意或所有类型,而不是所有类型(如number和boolean)都有length属性
return arg;
}
我们想要限制函数去处理任意带有 .length
属性的所有类型。 只要传入的类型有这个属性,我们就允许,就是说至少包含这一属性。 为此,我们需要列出对于 T
的约束要求。为此,我们定义一个接口来描述约束条件。 创建一个包含 .length 属性的接口,使用这个接口和 extends
关键字来实现约束:
interface Length {
length: number;
}
function ab<T extends Length>(val: T): number {
return val.length;
}
let a1 = ab('刘亦菲');
let a2 = ab([1,2,3,4]);
let a3 = ab({length: 33});
console.log('a1:', a1); //3
console.log('a2:', a2); //4
console.log('a3:', a3); //33
现在这个泛型函数被定义了约束,因此它不再是适用于任意类型。我们需要传入符合约束类型的值,必须包含必须的属性length,这就是泛型约束。
我们可以声明一个类型参数,且让它被另一个类型参数所约束。假设想要用属性名从一个对象里获取这个属性,并且我们想要确保这个属性存在于对象 obj上,因此我们需要在这两个类型之间使用约束。
interface ok {
[key: string]: any,
}
let getProps = (obj: ok, key: string): number => {
return obj[key];
}
let x = {'abc':1,b:2,10:3};
let q = getProps(x,'abc');
console.log(q); //1
let q1 = getProps(x,'d');
console.log(q1); //undefined
//K代表T中必须有的属性,用T来限制类型参数K
function getProperty<T,K extends keyof T>(obj: T,key: K) {
return obj[key];
}
let x1 = {'abc':1,b:2,10:3};
let q2 = getProperty(x1,10);
console.log(q2); //3
let q3 = getProperty(x1,'d'); //报错
console.log(q3);