typescript泛型

软件工程中,我们不仅要创建一致的定义良好的API,同时也要考虑可重用性。 组件不仅能够支持当前的数据类型,同时也能支持未来的数据类型,这在创建大型系统时为你提供了十分灵活的功能。

很多时候我们希望函数或者类可以支持多种数据类型,有很大的灵活性

例子:希望一个函数传入字符串或者数组,返回值为字符串或者数组,此时我们会想到函数重载还有联合类型

// 函数重载
function add(str:string): string;
function add(str:string[]): string[];
function add(str:any):any{  
    return str;
};

// 联合类型
function add(str:string|string[]): string|string[] {
    return str;
}

上面例子中,明显联合类型比函数重载更简洁一点,但是想要输入参数和返回类型是any,联合类型很显然不符合,函数重载倒是可以实现,然而使用函数重载,使用any类型会丢失了一些信息(无法进行类型约束),它忽略了输入参数和函数返回值的类型必须是一致的,调用者调用这个函数的时候无法获得约束关系,这个时候就要用到泛型了.

那么什么是泛型呢?
泛型是:不预先确定的类型,具体的类型在使用的时候才能确定
接下来我们来用泛型改造下add函数,下面的例子可以保证输入参数是any类型,又可以保证输入参数和返回值是一致的

// 泛型
function add<T>(str:T): T {
    return str;
}

// 可以传入任意类型的参数
add<number[]>([0,1,2]); // 可以指定类型,然后根据这个类型来传参
add([0,1,2]); // 也可以使用类型推断,直接传参
add('love me');
add({});

// 还可以使用泛型定义一个函数类型,使用类型定义
type Add = <T>(value:T) => T;
let add1:Add = add

 泛型接口

interface MyAdd {
    <T>(value:T):T
}
// 也可以用泛型接口来约束接口中的其他成员,只需要将T移到接口名称的后面
interface MyAdd<T> {
    (value:T):T
}

// 使用泛型接口的时候,实现的时候必须指定一个类型
function add<T>(str:T): T {
    return str;
}

let myadd:MyAdd = add; // 报错 泛型类型“MyAdd<T>”需要 1 个类型参数
let myadd:MyAdd<number> = add; // 正确
add(1); // 然后调用的时候就必须是number类型了

 泛型接口中也可以默认类型 

 // 默认字符串型
interface MyAdd<T = string> {
    (value:T):T
}

// 由于有默认类型,此时不指定类型也不会报错了
let myadd:MyAdd = add;
add('love me!'); // 只能传入字符串

泛型约束类的成员,

class Demo<T> {
    puls(value:T){
    console.log(value)
    return value;
    }
}
// 不设置类型的时候,参数为任意值
let d1 = new Demo();
d1.puls(1);
d1.puls('love me');
d1.puls({});

// 设置类型的时候,就被限制了,例如设置字符串数组,就必须输入字符串数组
let d2 = new Demo<string[]>();
d2.puls('love me'); // 报错---类型“"love me"”的参数不能赋给类型“string[]”的参数
d2.puls(['love','me']); // 正确

接下来我们来看下泛型约束 ,
首先,我们要打印出value的length,看下面这段代码是会报错的(类型“T”上不存在属性“length”)

/*
class Demo<T> {
    puls(value:T){
    console.log(value+','+value.length);// 类型“T”上不存在属性“length”
    return value;
    }
}
*/

我们要怎么解决呢?
这个时候我们需要用到乐行约束,此时需要不过需要定义一个带有length属性的接口,让T来继承下就ok了
T继承了Length,就意味着受到了一定的约束,就不再是任何类型都可以传了,输入的参数不管是什么类型,但一定要有length属性

// 定义length接口
interface Length{
    length:number
}
class Demo<T extends Length> {
    puls(value:T){
        console.log(value+','+value.length);// 由于T继承了Length,有了length属性,所以就不会报错了
        return value;
    }
}

// 实例化
// T继承了Length,就意味着受到了一定的约束,就不再是任何类型都可以传了,输入的参数不管是什么类型,但一定要有length属性
let d1 = new Demo();
d1.puls(false); // 报错  -- 类型“false”的参数不能赋给类型“Length”的参数
d1.puls('123');
d1.puls([1,2,3]);
d1.puls({length:123});

// 函数也是一样的道理
function demo<T extends Length>(value:T){
    console.log(value+','+value.length);
}
demo(false); // 报错  -- 类型“false”的参数不能赋给类型“Length”的参数
demo('123'); demo([1,2,3]); demo({length:123}); // 正确

最后注意一点:泛型不可以应用于类的静态成员,下面这种写法是错误的

/*
class Demo<T> {
    static puls(value:T){ // 报错---静态成员不能引用类类型参数。
        console.log(value);
        return value;
    }
}
*/

 

泛型的好处:

1.函数和类可以轻松的支持多种类型,增强程序的扩展性
2.不必写多条函数重载,冗长的联合类型声明,增强代码可读性
3.灵活控制类型之间的约束

 

总结小案例:


interface Person {
  name: string
  age: number
}

interface Animal {
  name: string
  type: string
}

function aa(arg:Person): Person{
  console.log(arg)
  const s = {
    name:arg.name,
    age:arg.age
  }
  return s;
}

/*
* 尖括号里面的是添加了P,A类型变量,arg1参数使用P类型,arg2使用A类型,返回值使用P类型.
* */
function identity<P,A>(arg1: P,arg2:A):P {
  return arg1
}
/*
* 1.因为参数和返回值被约束了,所以不可以随意的传参,一定要按照约束的类型来传参
* 2.调用的时候,给identity添加了上面已经定义了的Person和Animal两个变量类型,
*   arg1传入的类型跟Person的数据类型保持一致,arg1传入的类型跟Animal的数据类型保持一致,
*   得到的返回值是{name:'aaa',age:0},正好是Person的类型;
* */
identity<Person,Animal>({name:'aaa',age:0},{name:'aaa',type:'cat'});

 

 

 

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值