在软件工程领域,我们不仅要创建定义一致良好的 API,也需要同时考虑重用性,泛型就给予了这样的灵活性但又不失优雅
泛型之 Hello World
先定义如下的一个函数,这个函数会返回任何传入它的值。
function getValue(a: number): number {
return a;
}
上面我们定义的函数参数是一个数字类型的,返回值当然也是数字类型的。
假如我们需要传递一个字符串参数并返回,那么就必须再定义一个函数
function getValue(a: number): number {
return a;
}
function getValue1(a: string): string {
return a;
}
但是它们两个函数的功能都是一致的,只是参数的类型不一致而已。
而且假如要扩展其他数据类型的话,难道都要再写一次嘛?
或者,我们使用any
类型来定义函数:
function getValue(a: any): any {
return a;
}
但是使用 any 类型就丢失了一些信息:传入的值必须与返回的值是同一种类型。因为 any
体现不出这个关系。
但是 **泛型 ** 可以定义一致的接口,同时考虑代码重用性
这里我们使用 类型变量,它是一种特殊的变量,只表示类型而不是值
字母保持一致就好,一般使用 T 表示 (Type)
function getValue<T>(a: T): T {
return a;
}
getValue<number>(3);
getValue<string>("qzy");
我们把这个版本的函数叫做泛型,因为它可以适用于多个类型
我们定义了泛型函数后,可以用两种方法使用。
第一种是,传入所有的参数,包含类型参数:
getValue<string>("abc");
第二种方法更普遍。利用了类型推论 -- 即编译器会根据传入的参数自动地帮助我们确定 T 的类型:
getValue(1);
使用泛型变量
使用泛型创建函数时,必须要在函数体内正确的使用这个类型
function getValue<T>(a: T): T {
console.log(a.length);
return a;
}
上面的例子中,我们想访问传入参数的 length属性。
但是这个 T 有可能是一个数字类型,而数字类型是不存在 length 属性的。因此编译器就报错了。
function getValue<T>(a: T): T {
// 类型“T”上不存在属性“length”
console.log(a.length);
return a;
}
为此我们定义一个接口来描述条件。
interface LengthProps {
length: number;
}
function getValue<T extends LengthProps>(a: T): T {
console.log(a.length);
return a;
}
getValue([1, 2]);
getValue(1); // 类型“number”的参数不能赋给类型“LengthProps”的参数
泛型接口
泛型接口有两种方式
第一种:
interface config {
<T>(val: T): T;
}
// fn: config
// function 后面要与接口定义的一致
let fn: config = function <T>(val: T): T {
return val;
};
fn(5);
第二种:
interface config<T> {
(val: T): T;
}
function configFn<T>(val: T): T {
return val;
}
// config<string>
// = configFn
let fn: config<string> = configFn;
fn("qzy");
泛型约束-类型参数
你可以声明一个类型参数,且它被另一个类型参数所约束。
比如,现在我们想要用属性名从对象里获取这个属性,并且我们想要确保这个属性存在于对象 obj
上,因此我们需要在这两个类型之间使用约束。
function getProperty<T, K>(obj: T, key: K) {
// 类型“K”无法用于索引类型“T”
return obj[key];
}
let x = { a: 1, b: 2, c: 3, d: 4 };
getProperty(x,'e');
我们代码的意图是,通过传入一个泛型的对象,或者什么其他的变量,然后再传入另外一个泛型的变量,找到 obj 下面的 key 属性。
这个时候就你的编辑器就会报这样的错,我们传入的泛型key变量,不一定是存在于泛型obj中的属性。
为解决这一问题,我们可以使用 keyof
function getProperty<T, K extends keyof T>(obj: T, key: K) {
// 类型“K”无法用于索引类型“T”
return obj[key];
}
let x = { a: 1, b: 2, c: 3, d: 4 };
getProperty(x, "b");
getProperty(x,'e'); // 类型“"e"”的参数不能赋给类型“"b" | "a" | "c" | "d"”的参数
K extends keyof T
将两个类型变量关系起来