2.11 泛型(Generics)
2.11.1 泛型在函数中的使用
函数类型推论不会流入到函数体内,所以使用表达式没法明确建立类型的绑定,用泛型可以帮我们打破这个对应的问题。
- 泛型是指在 定义函数接口或类时,不预先指定具体类型,而是在使用时再指定类型的一种特征。
- 可以把它看作一个占位符,等确定类型后再动态的填入。
- 在普通函数名之后,使用尖括号<T>,来表示泛型类型参数的声明,表示定义了一个名为
T的类型变量(Type 的缩写)。它是一个占位符,在函数调用时会被具体的类型替换。- T是一般写法,可自定义其他字母,需大写
- 多个参数类型声明,需先在尖括号中声明,后使用
// 参数的类型使用泛型---> arguments: T 表示参数的类型是泛型T
// 函数的返回值类型,则在参数与花括号之间,仍是使用冒号
function getArguments<T> (arg: T): T {
return arg;
}
// result的类型会在传入参数后,自动识别为字符串类型
const result = getArguments('str');
// 多个参数 泛型声明、使用
function swap<T, U> (tuple: [T, U]): [U, T] {
return [tuple[1], tuple[0]];
}
// 根据传入的参数,T及为字符串类型,U为数字类型
// const result2: [number, string]
const result2 = swap(['str', 123]);
2.11.2 约束泛型 extends
前提:在函数内部使用变量时,我们不知道它的类型,所以不能使用它的属性及方法。
function func<T> (arguments: T): T {
// Property 'length' does not exist on type 'T'
// 报错 泛型T 上不一定包含length属性
console.log(arguments.length);
return arguments;
}
// length 应该作用给一个T 类型的 array
// 在参数的泛型后跟[],T[]表示 包含T类型的数组
function func2<T> (arguments: T[]): T[] {
console.log(arguments.length);
return arguments;
}
// 此时arr就可以正确的返回 const arr: number[]
// 是一个包含数字类型的数组
const arr = func2([1,2,3]);
- 但如果我们只是想要传入的参数存在length属性即可,那以上方案不适合。
- 所以需要约束指定属性类型,通过
interface和extends关键字配合使用,实现约束。
interface IWithLength {
length: number
}
function widthLength<T extends IWithLength> (arg: T): T {
console.log(arg.length);
return arg;
}
const str = widthLength('str'); // const str: string;
const obj = widthLength({length: 5}); // const obj: { length: number };
const arr2 = widthLength([1,2,3]); // const arr2: number[]
// 这样就实现了,不论你是什么类型,只要有length属性,都通过校验。
// 同样内部有多个属性也不会报错,只要约束内的属性存在即可
- 约束泛型,也可以约束更多属性
2.11.3 泛型在类和接口中的使用
- 泛型在类上的使用
class Queue<T> {
private data: T[] = []; // private 私有属性
push(item: T) {
return this.data.push(item);
},
pop() {
return this.data.shift();
}
}
// 通过设定好的类,生成一个构造函数,指定泛型为数字类型
const queue = new Queue<number>();
queue.push(1);
// const poped: number | undefined
const poped = queue.pop();
if (poped) {
poped.toFixed();
}
- 泛型在接口中的使用
interface KeyPair<T, U> {
key: T;
value: U;
}
let kp1: KeyPair<number, string> = {key: 1, value: 'str'};
let kp2: KeyPair<string, number> = {key: 'str', value: 123};
- 拓展
let arr: number[] = [1,2,3];
// 转化为泛型定义
// Array 关键词后的尖括号中,填写的就是 数组元素的类型
let arrTwo: Array<number> = [4,5,6];
2.11.4 类型别名 type
- 给类型起别名 使用type关键字
// 给函数类型起别名
type PlusType = (x: number, y: number) => number;
let sum: PlusType = (x,y) => x+y;
const result = sum(2, 3);
// 联合类型
type StrOrNumber = string | number;
let result2: StrOrNumber = '123'; // 可以是字符串
result2 = 123; // 也可以是数字
// 原始类型
const str: 'name' = 'name'; // 只能是字符串name
const number: 1 = 1; // 只能是1
type Directions = ‘Up' | 'Down' | 'Left' | 'Right';
// 这里在输入时,会自动提示已限制定义的四种类型,除此之外都不可。
let toWhere: Directions = 'Up';
2.11.5 交叉类型 &
有点像interface,为了实现对象的组合形式和扩展
interface IName {
name: string;
}
type IPerson = IName & {age: number};
let Person: IPerson = {name:'123',age: 123};
什么时候使用interface? 什么时候使用type?
- type只是别名,可以看作快捷方式。
- 当要使用交叉或组合类型时,可以考虑使用type
- 当要实现
extends或 实现类的 **implements**时,可以考虑interface
// 接口继承
interface A { x: number; }
interface B extends A { y: string; }
// 类实现接口
class C implements B {
x = 0;
y = "hello";
}