这是我参与更文挑战的第十一天,活动详情查看:更文挑战
背景: 很多时候,我们希望一个函数或者类可以支持多种数据类型
栗子, 一个打印函数的改造: 函数会返回任何传入它的值
function log(value: string): string {
console.log(value);
return value;
}
复制代码
1.改造为接受一个数组参数
方式一:函数重载
function log(value: string): string;
function log(value: string[]): string[];
function log(value: any) {
console.log(value);
return value;
}
复制代码
方式二: 联合类型
function log(value: string | string[]): string | string[] {
return value;
}
复制代码
2. 希望 log 函数可以接受任意类型的参数
方式一: any 类型
但是,丢失了类型之间的约束关系,忽略了参数类型与函数的返回值类型必须是一致的
function log(value: any): any {
console.log(value);
return value;
}
复制代码
方式二: 泛型 ?(查看官方文档)
1. 通过泛型来定义函数-泛型函数
概念: 不预先确定数据类型,具体的类型在使用的时候才能确认
function log<T>(value: T): T {
return value;
}
//调用方式一: 在调用的时候 指定 T的类型
console.log(
log<string[]>(["a", "b"]),
);
// 调用方式二: 利用TS的类型推断,省略 函数的参数类型 --- 推荐
console.log(log(["a", "b"]));
复制代码
2. 通过泛型来定义函数类型–泛型函数类型
泛型可以定义一个函数,也可以定义一个函数类型
function log<T>(value: T): T {
return value;
}
/*
类型别名-------------
使用类型别名定义一个泛型函数类型,
等号后面跟函数签名差不多, 但是要把函数的名称去掉
*/
type Log = <T>(value: T) => T;
// 泛型函数实现
let myLog: Log = log;
复制代码
或
function identity<T>(arg: T): T {
return arg;
}
let myIdentity: <T>(arg: T) => T = identity;
let myIdentity1: <U>(arg: U) => U = identity;
复制代码
3.泛型接口
在这里,泛型仅仅约束了一个函数,但我们也可以用泛型来约束接口的其他成员
// 和类型别名的方式是完全等价的
interface Log {
<T>(value: T): T;
}
复制代码
用泛型来约束接口的其他成员: 把泛型放在接口名称的后面,这样接口的所有成员都能受到泛型的约束了
interface Log<T> {
(value: T): T;
}
复制代码
注:泛型变量约束了整个接口后,在实现的时候,必须指定一个类型
function log<T>(value: T): T {
return value;
}
interface Log<T> {
(value: T): T;
}
// 错误提示: 泛型类型“Log<T>”需要 1 个类型参数。ts(2314)
let myLog: Log = log;
// 解决
let myLog1: Log<number> = log;
// myLog1的参数只能是 number
myLog1(1);
复制代码
也可以在接口的定义中指定一个默认类型
// 指定默认类型
interface Log<T = Array<string>> {
(value: T): T;
}
let myLog: Log = log;
myLog(["1", "2"]);
复制代码
泛型也可以约束类的成员
泛型类
把泛型变量放在类的名称后面,就可以约束所有类的成员了
注意:泛型不能约束类的静态成员
class Test<K> {
// 错误提示: 静态成员不能引用类类型参数。ts(2302)
// static eat(param:T){
// }
run(value: K) {
return value;
}
}
复制代码
实例的方法将会受到泛型的约束
// 实例化
let log1 = new Test<number>();
// 错误提示: 类型“"a"”的参数不能赋给类型“number”的参数。ts(2345)
// log1.run('a')
log1.run(1);
复制代码
实例化时可以不传入类型参数
当没有指定参数时, 实例方法的参数类型可以是任意的
let log2 = new Test();
log2.run("a");
log2.run(1);
复制代码
泛型约束
function testA<T>(value: T): T {
// 类型“T”上不存在属性“length”。ts(2339)
console.log(value, value.length);
return value;
}
复制代码
解决: T 继承 Length 接口,表示 T 受到了约束,即输入的参数必须具有 length 属性
interface Length {
length: number;
}
function testA<T extends Length>(value: T): T {
console.log(value, value.length);
return value;
}
testA([1]);
testA("12212");
testA({ length: 1 });
/*
错误提示:
类型“{ a: number; }”的参数不能赋给类型“Length”的参数。
对象文字可以只指定已知属性,并且“a”不在类型“Length”中。ts(2345)
*/
// testA({a:1})
复制代码
泛型的好处
- 函数和类可以轻松地支持多种类型,增强程序的扩展性
- 不必写多条函数重载,冗长的二联合类型声明,增强代码可读性
- 灵活控制类型之间的约束
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END