泛型定义
在使用函数,接口,类时,无法确定最终返回值的数据类型,可以像定义参数一样定义类型。
将类型当参数传入,最终返回实际使用的类型,使用泛型可以明确返回类型。
简单例子
function f5(arg:any) {
return arg
}
let e = f5('234') //any 而不是string
//使用泛型,不同入参类型确定最终返回类型
function f4<T>(arg:T):T {
return arg
}
let q = f4<number>(123) //number
let w = f4('qwe') //string 通过参数的类型也可以推导出类型
function createArr<T>(length:number,value:T):T[] {
let result:T[] = []
for (let i=0;i<length;i++){
result[i] = value;
}
return result
}
createArr(3,'t') //string[]
createArr<number>(4,34) //手动指定类型 number[]
createArr(4,34) //也可以不用手动,自动推导出来类型 number[]
多个类型参数
定义泛型的时候可以一次性定义多个参数
function f6<T,U>(tuple:[T,U]):[U,T] {
return [tuple[1],tuple[0]]
}
let q = f6([3,'three']) //['three',3] [string,number]
泛型约束
由于不能明确参数是哪种类型,所以不能随便访问和操作它的属性或方法,这时候可以使用泛型约束
function f7<T>(arg:T) {
return arg.length //TS2339: Property 'length' does not exist on type 'T'.
//arg类型不确定,不一定包含属性length,所以编译报错了
}
//使用泛型约束 关键字 extends
function f8<T extends T[]>(arg:T):number {
return arg.length
}
//也可定义用有length属性的接口
interface LengthProps{
length:number
}
function f9<T extends LengthProps>(arg:T):T {
console.log(arg.length)
return arg
}
//如果传入的实际参数没有拥有length属性 编译还是会爆粗欧
f9(123)
//TS2345: Argument of type 'number' is not assignable to
//parameter of type 'LengthProps'.
****多个类型参数之间互相约束
function f10<T extends U,U>(target:T,source:U):T {
for (let id in source){
target[id] = source[id] //error source[id]不可分配给target[id]。target可以有source没有的属性
target[id] = (<T>source)[id] //OK 所以使用(<T>source)[id] 来约束source
}
return target
}
let x = { a: 1, b: 2, c: 3, d: 4 };
f10(x, { b: 10, d: 20 });
泛型类型
类型本身 可以被定义为拥有不明确的类型参数的泛型,并且可以接收明确类型作为入参,从而衍生出更具体的类型
function reflect<P>(param: P): P {
return param;
}
const reflectFn: <P>(param: P) => P = reflect //ok
// 为变量reflectFn 注解了 泛型类型 并且将函数 reflect 作为值 赋给他
/*也可以吧 reflectFn 的类型注解 提取作为一个能被服用的类型别名或者接口
* */
interface IReflectFun {
<P>(param: P): P
}
const reflectFn2: ReflectFuntion = reflect;
const reflectFn3: IReflectFun = reflect;
/*将类型入参的定义 移动到 类型别名或接口名称后, 定义一个 接收 具体类型入参后
* 返回一个新类型的类型 就是泛型类型 */
interface GenericIdentityFn {
<T>(arg: T): T;
}
function identity<T>(arg: T): T {
return arg;
}
let myIdentity: GenericIdentityFn = identity;
一个相似的例子,我们可能想把泛型参数当作整个接口的一个参数。 这样我们就能清楚的知道使用的具体是哪个泛型类型(比如: Dictionary而不只是Dictionary)。 这样接口里的其它成员也能知道这个参数的类型了。
interface GenericIdentityFn<T> {
(arg: T): T;
}
function identity<T>(arg: T): T {
return arg;
}
let myIdentity: GenericIdentityFn<number> = identity;
注意,我们的示例做了少许改动。 不再描述泛型函数,而是把非泛型函数签名作为泛型类型一部分。 当我们使用 GenericIdentityFn的时候,还得传入一个类型参数来指定泛型类型(这里是:number),锁定了之后代码里使用的类型。 对于描述哪部分类型属于泛型部分来说,理解何时把参数放在调用签名里和何时放在接口上是很有帮助的。
泛型接口
可以使用含有泛型的接口来定义函数的形状:
interface CreateArrFunc {
<T>(length:number,value:T):T[]
}
let createArr1:CreateArrFunc
createArr1 = function<T>(lengt:number,val:T):T[] {
let result:T[] = []
for (let i=0;i<length;i++){
result.push(val)
}
return result
}
createArr1(5,Math.random()*10) //number[]
进一步,我们可以把泛型参数提前到接口名上
interface CreateArrFunc {
<T>(length:number,value:T):T[]
}
let createArr2:CreateArrFunc<number>
createArr2 = function<T>(lengt:number,val:T):T[] {
let result:T[] = []
for (let i=0;i<length;i++){
result.push(val)
}
return result
}
createArr1(5,Math.random()*10) //number[]
注意,此时在使用泛型接口的时候,需要定义泛型的类型。
泛型类
与泛型接口类似,泛型也可以用于类的类型定义中:
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类型。 也可以使用字符串或其它更复杂的类型。
let stringNumeric = new GenericNumber<string>();
stringNumeric.zeroValue = "";
stringNumeric.add = function(x, y) { return x + y; };
console.log(stringNumeric.add(stringNumeric.zeroValue, "test"));
与接口一样,直接把泛型类型放在类后面,可以帮助我们确认类的所有属性都在使用相同的类型。
类有两部分:静态部分和实例部分。 泛型类指的是实例部分的类型,所以类的静态属性不能使用这个泛型类型。