Appearance
泛型
泛型有两个作用 一是在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型。适用于声明时不能确定类型,使用时才能确定类型。 二是约束某个类型必须有次功能
ts
interface IClass <T> {
new (name: string): T
}
const createInstance<T> = (class: IClass<T>, name: string) => {
return new class(name)
}
const i = createInstance<Person>(Person, 'foo')
// 调用时 <> 内传入了类型,传给函数后,函数又传给接口。函数中使用泛型
ts
createArray
实现一个函数 createArray,它可以创建一个指定长度的数组,同时将每一项都填充一个默认值:
typescript
function createArray<T>(length: number, value: T): Array<T> {
let result: T[] = [];
for (let i = 0; i < length; i++) {
result[i] = value;
}
return result;
}
createArray<string>(3, 'x');我们定义了泛型函数后,可以用两种方法使用。 第一种是,传入所有的参数,包含类型参数:
typescript
createArray<string>(3, 'x');第二种方法更普遍。利用了类型推论,让类型推论自动推算出来,帮助我们保持代码精简和高可读性。 如果编译器不能够自动地推断出类型的话,只能像上面那样明确的传入T的类型,在一些复杂的情况下, 这是可能出现的。
typescript
createArray(3, 'x');多个类型参数
定义泛型的时候,可以定义多个类型参数
typescript
function swap<T, U>(tuple: [T, U]): [U, T] {
return [tuple[1], tuple[0]];
}
swap([7, 'seven']); // ['seven', 7]
// 写在函数上的泛型表示调用函数时,传入具体类型。
interface Swap {
<T, U>(tuple: [T, U]): [U, T]
}
const swap: Swap = <T, U>(tuple: [T, U]): [U, T] => {
return [tuple[1], tuple[0]];
}
// 写在接口上的泛型,表示使用接口时传入类型。
interface Swap<T, U> {
(tuple: [T, U]): [U, T]
}
const swap: Swap:<string, number> = <T, U>(tuple: [T, U]): [U, T] => {
return [tuple[1], tuple[0]];
}泛型约束
ts
// 只要有 length 就相加
function sum = <T extends { length: number }>(a: T, b: T): T => {
return (a + b) as T
}
sum('123', [1])默认泛型
ts
interface O<T=string> {
name: T
}
type T1 = O
type T2 = O<number>
type T3 = O<boolean>
let o: T1 = {
name: 'string'
}
let o2:T2 = {
name: 1
}使用泛型变量
使用泛型创建像identity这样的泛型函数时,编译器要求你在函数体必须正确的使用这个通用的类型。 换句话说,你必须把这些参数当做是任意或所有类型。
typescript
function loggingIdentity<T>(arg: T): T {
console.log(arg.length); // Error,arg不一定包含属性 length,所以编译的时候报错了。
return arg;
}如果这么做,编译器会报错说我们使用了arg的.length属性,但是没有地方指明arg具有这个属性。 记住,这些类型变量代表的是任意类型,所以使用这个函数的人可能传入的是个数字,而数字是没有 .length属性的。
现在假设我们想操作T类型的数组而不直接是T。由于我们操作的是数组,所以.length属性是应该存在的。 我们可以像创建其它数组一样创建这个数组:
typescript
function loggingIdentity<T>(arg: T[]): T[] {
console.log(arg.length); // Array has a .length, so no more error
return arg;
}你可以这样理解loggingIdentity的类型:泛型函数loggingIdentity,接收类型参数T和参数arg,它是个元素类型是T的数组,并返回元素类型是T的数组。 如果我们传入数字数组,将返回一个数字数组,因为此时 T的的类型为number。 这可以让我们把泛型变量T当做类型的一部分使用,而不是整个类型,增加了灵活性。
我们也可以这样实现上面的例子:
typescript
function loggingIdentity<T>(arg: Array<T>): Array<T> {
console.log(arg.length); // Array has a .length, so no more error
return arg;
}泛型约束
typescript
// 使用了 extends 约束了泛型 T 必须符合接口 Lengthwise 的形状,也就是必须包含 length 属性。
// 如果调用 loggingIdentity 的时候,传入的 arg 不包含 length,那么在编译阶段就会报错了:
interface Lengthwise {
length: number;
}
function loggingIdentity<T extends Lengthwise>(arg: T): T {
console.log(arg.length);
return arg;
}ts
// keyof 可以取出对象中的属性
const getVal = <T extends Object,K extends keyof T>(target: T, key: K) => {
}
getVal({ a: 1, b: 2 }, 'a')
type T1 = keyof any // key 可以是 number、string、symbol
type T2 = keyof (string | number)泛型修饰类
ts
class Ary<T> {
ary: T[] = []
add (v: T) {
this.ary.push(v)
}
}多个类型参数之间也可以互相约束:
typescript
// 使用了两个类型参数,其中要求 T 继承 U,这样就保证了 U 上不会出现 T 中不存在的字段。
function copyFields<T extends U, U>(target: T, source: U): T {
for (let id in source) {
target[id] = (<T>source)[id];
}
return target;
}
let x = { a: 1, b: 2, c: 3, d: 4 };
copyFields(x, { b: 10, d: 20 });泛型接口
可以使用带有调用签名的对象字面量来定义泛型函数:
typescript
createArray = function<T>(length: number, value: T): Array<T> {
let result: T[] = [];
for (let i = 0; i < length; i++) {
result[i] = value;
}
return result;
}
let createArray: {<T>(length: number, value: T): Array<T>} = createArray;把上面例子里的对象字面量拿出来做为一个接口:
typescript
interface CreateArrayFunc {
<T>(length: number, value: T): Array<T>;
}
let createArray: CreateArrayFunc;
createArray = function<T>(length: number, value: T): Array<T> {
let result: T[] = [];
for (let i = 0; i < length; i++) {
result[i] = value;
}
return result;
}我们可能想把泛型参数当作整个接口的一个参数。 这样我们就能清楚的知道使用的具体是哪个泛型类型,这样接口里的其它成员也能知道这个参数的类型了。
typescript
interface CreateArrayFunc<T> {
(length: number, value: T): Array<T>;
}
// 此时在使用泛型接口的时候,需要定义泛型的类型。
let createArray: CreateArrayFunc<any>;
createArray = function<T>(length: number, value: T): Array<T> {
let result: T[] = [];
for (let i = 0; i < length; i++) {
result[i] = value;
}
return result;
}泛型类
泛型类看上去与泛型接口差不多。 泛型类使用 <> 括起泛型类型,跟在类名后面。
typescript
class GenericNumber<T> {
zeroValue: T;
add: (x: T, y: T) => T;
}
let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function(x, y) { return x + y; };GenericNumber类的使用是十分直观的,并且你可能已经注意到了,没有什么去限制它只能使用number类型。 也可以使用字符串或其它更复杂的类型。
typescript
let stringNumeric = new GenericNumber<string>();
stringNumeric.zeroValue = "";
stringNumeric.add = function(x, y) { return x + y; };
console.log(stringNumeric.add(stringNumeric.zeroValue, "test"));与接口一样,直接把泛型类型放在类后面,可以帮助我们确认类的所有属性都在使用相同的类型。
我们在类那节说过,类有两部分:静态部分和实例部分。 泛型类指的是实例部分的类型,所以类的静态属性不能使用这个泛型类型。
泛型参数的默认类型
可以为泛型中的类型参数指定默认类型。当使用泛型时没有在代码中直接指定类型参数,从实际值参数中也无法推测出时,这个默认类型就会起作用。
typescript
function createArray<T = string>(length: number, value: T): Array<T> {
let result: T[] = [];
for (let i = 0; i < length; i++) {
result[i] = value;
}
return result;
}