Typescript 实战 --- (6)泛型
1、什么是泛型?
不预先确定的数据类型,具体的类型在使用的时候才能确定
function log<T>(value: T) { return value; } // 调用方式 log<string[]>(['a', 'b']) // 还可以根据类型推断省略掉类型的参数 log(['a', 'b'])
如果使用 any类型来定义这个函数,则意味着该函数传入的参数和返回值都可能是任意类型,例如:传入一个字符串类型,返回的却是数字类型
function log(value: any): any { return value; }
2、泛型函数类型
function identity<T>(arg: T): T { return arg; } let myIdentity: <T>(arg: T) => T = identity;
3、泛型接口
interface Log1 { <T>(value: T): T // 使用泛型约束一个函数 } interface Log2<T> { // 使用泛型约束接口的所有成员 (value: T): T } function log<T>(value: T): T { return value; } // 实现泛型接口的时候,必须指定类型 let myLog: Log2<number> = log; myLog(2); // 如果不指定类型,也可以在泛型接口中指定一个默认的类型 interface Log3<T = string> { (value: T): T } let myLog3: Log3 = log; myLog3('hello');
4、泛型类
泛型不能用来约束类的静态成员,只能约束类的实例成员
class Log<T> { run(value: T): T { return value } // static foo(value: T): T { return value } // 静态成员不能引用类类型参数 } let log1 = new Log<number>(); log1.run(2); let log2 = new Log(); log2.run('hello');
5、泛型约束
上例中的 log函数,如果我们想要访问其函数参数的length属性,程序会报错,因为不是所有类型都有length属性
function log<T>(value: T): T { console.log(value.length); // 类型“T”上不存在属性“length” return value; }
为此,我们要列出对 T 的约束要求,传入的类型必须有这个属性才行。可以通过定义一个接口来描述约束条件,使用这个接口和extends关键字来实现约束
interface HasLength { length: number; } function log<T extends HasLength>(value: T): T { console.log(value.length); return value; } log([1]); // 1 log('hello'); // 5 log({length: 2}); // 2 log(2); // 类型“2”的参数不能赋给类型“HasLength”的参数
6、使用泛型的好处
(1)、增强程序的可扩展性:函数或类可以很轻松的支持多种数据类型
(2)、增强代码的可读性:不必写多条函数重载,或者冗长的联合类型声明
(3)、灵活的控制类型之间的约束