前端开发系列045-基础篇之TypeScript语言特性(五)

本文主要对TypeScript中的泛型进行展开介绍。主要包括以下内容

❏ 泛型函数类型 ❏ 泛型接口(Interface) ❏ 泛型类(Class) ❏ 泛型约束

一、泛型函数的类型

在以前的文章中,我们已经介绍了什么是泛型函数,它跟普通函数还是有些区别的(泛型函数使用类型变量来占位,具体类型值由函数调用传参决定)。以前文章中介绍过TypeScript中的数据类型,以及可选的类型声明。虽然并没有必要(因为可以通过类型推导机制推导出来),但我们确实能够抽取出普通函数的具体类型。下面代码中demo函数的函数类型为:(name:string,age:number) => string

typescript 复制代码
//文件路径 ../08-泛型函数/03-函数的类型.ts

//[001] 函数的类型
//(1) 声明demo函数
function demo(name:string,age:number):string
{
  return "姓名:" +name + "年龄:" + age;
}

//(2) 把demo函数赋值给f
let f:(name:string,age:number)=>string = demo;
//使用demo函数的调用签名
//let f:{(name:string,age:number):string} = demo;
console.log(f("zs",18));    //姓名:zs年龄:18

接下来,我们花点时间研究,泛型函数的函数类型。其实泛型函数的类型与非泛型函数的类型本质上并没由什么不同,只是在最前面增加一个类型变量参数而已。下面给出具体的代码示例。

c++ 复制代码
function demoT<T>(arg:T):T{
  return arg;
}
//泛型函数demoT的类型为:<T>(arg:T) =>T
let f1 : <T>(arg:T) =>T = demoT;
//使用带有调用签名的对象字面量来定义泛型函数
let f2 : {<T>(arg:T) :T} = demoT;
//可以使用不同的泛型参数名(这里为X)
let f3 : <X>(arg:X) =>X = demoT;
//不使用类型声明
let f4 = demoT;

console.log(f1("abc"));     //abc
console.log(f2("哈哈"));     //哈哈
console.log(f3("嘿嘿"));     //嘿嘿
console.log(f4("咕噜"));     //咕噜

泛型函数的类型声明可以使用不同的泛型参数,只要数量和使用方式一致即可。

二、泛型接口(Interface)

接口(Interface) 指在面向对象编程语言中,不包含数据和逻辑但使用函数签名定义行为的抽象类型。

TypeScript提供了接口特性,TypeScript的接口可以定义数据和行为,也可以扩展其它接口或者类。

在传统面向对象编程范畴中,一个类可以被扩展为另外一个类,也可以实现一个或多个接口。实现某个接口可以被看做是签署了一份协议,接口相当于协议,当我们签署协议(实现接口)后,就必须遵守它的规则。

接口本身是抽象类型,其内容(规则)就是属性和方法的签名。

在前文中我们定义了泛型函数demoT,可以把demoT函数的签名抽取并定义接口GenericFn,下面给出示例代码。

csharp 复制代码
//文件路径 ../08-泛型函数/04-泛型接口.ts

//(1) 声明泛型函数demoT
function demoT<T>(arg:T):T{
  return arg;
}

//(2) 定义GenericFn接口
interface GenericFn{
    <T>(arg: T): T;
}

let fn: GenericFn = demoT;
console.log(fn("哈哈"));  //哈哈

有时候,我们可能需要把泛型参数(T)抽取成为整个接口的参数,好处是抽取后我们能够清楚的知道使用的具体泛型类型是什么,且接口中的其它成员也能使用。当我们使用泛型接口的时候,传入一个类型参数来指定泛型类型即可,下面给出调整后的示例代码。

typescript 复制代码
//文件路径 ../08-泛型函数/05-泛型接口02.ts

//(1) 声明泛型函数demoT
function demoT<T>(arg:T):T{
  return arg;
}

//(2) 定义泛型接口
interface GenericFn<T>{
    (arg: T): T;
}

let f1: GenericFn<number> = demoT;
console.log(f1(123));       //123
//报错:Argument of type '"字符串"' is not assignable to parameter of type 'number'.
//console.log(f1("字符串")); //错误的演示

let f2: GenericFn<string> = demoT;
console.log(f2("字符串")); //字符串

三、泛型类(Class)

泛型特性可以应用在Class身上,具体的使用方式和接口差不多。

ini 复制代码
//文件路径 ../08-泛型函数/06-泛型类.ts

//泛型类(Class)
class Person<T>{
  //[1] 属性部分
  name:T;
  color:T;
  //[2] 方法部分
  add:(a:T,b:T)=>T;
}

//获取实例对象p1
var p1 = new Person<string>();
p1.name = "张三";

//报错: TS2322: Type '123' is not assignable to type 'string'.
//p1.name = 123;  错误的演示
p1.color = "Red";
p1.add = function(a,b){
  return a + b;
}
console.log(p1);                      //{name:"张三",color:"Red",...}
console.log(p1.add("ABC","-DEF"));    //ABC-DEF


//获取实例对象p2
var p2 = new Person<number>();
p2.name = 0;
p2.color = 1;
p2.add = function(a,b){
  return a + b;
}
console.log(p2.add(100,200));         //300

上面的代码提供了泛型类使用的简单示例,在定义泛型类的时候,只需要直接把泛型类型放在类名(这里为Person)后面即可,通过new调用类实例化的时候,以<类型>的方式传递,在Class中应用泛型可以帮助我们确认类中的很多属性都在使用相同的类型,且能够优化代码结构。

四、泛型约束

有时候,我们可能需要对泛型进行约束。下面的代码中我们声明了泛型函数fn,并在fn的函数体中执行console.log("打印length值 = " + arg.length);意在打印参数的长度。这份代码在编译的时候会报错,因为无法确定函数调用时传入的参数一定拥有length属性。

lua 复制代码
//文件路径 ../08-泛型函数/02-泛型函数使用注意点.ts
//说明 该泛型函数使用类型变量T来表示接收参数和返回值的类型
function fn<T>(arg:T):T{
  console.log("打印length值 = " + arg.length);
  return arg;
}
//报错:error TS2339: Property 'length' does not exist on type 'T'.
console.log(fn([1,2,3]));

其实相比于操作any所有类型的数据而言,在这里我们需要对参数类型进行限制,要求传入的参数能够拥有length属性,这种场景可以使用泛型约束。

理想中泛型函数fn的工作情况是:"只要传入的参数类型拥有指定的属性length,那么代码就应该正常执行。 为此,需要列出对于T的约束要求。下面,我们先定义一个接口来描述特定的约束条件。然后使用这个接口和extends关键字来实现泛型约束,代码如下:

typescript 复制代码
//文件路径 ../08-泛型函数/07-泛型约束.ts

//[001] 定义用于描述约束条件的接口
interface hasLengthP
{
  length: number;
}

//[002] 声明fn函数(应用了泛型约束)
function fn<T extends hasLengthP>(arg:T):T
{

  console.log("打印length值 = " + arg.length);
  return arg
}

//[003] 调用测试
console.log(fn([1,2,3]));   //打印length值 = 3 [1,2,3];
console.log(fn({name:"zs",length:1})); //打印length值 = 1 对象内容

//说明:字符串会被转换为对象类型(基本包装类型)
console.log(fn("测试"));    //打印length值 = 2 测试

//报错:error TS2345: Argument of type '123' is not assignable to parameter of type 'hasLengthP'.
console.log(fn(123));   //错误的演示

上面代码中的fn泛型函数被定义了约束,因此不再是适用于任意类型的参数。我们需要传入符合约束类型的值,传入的实参必须拥有length属性才能运行。

泛型约束中使用多重类型

提示 当声明泛型约束的时候,我们只能够关联一种类型。但有时候,我们确实需要在泛型约束中使用多重类型,接下来我们研究下它的可能性和实现方式。

假设现在有一个泛型类型需要被约束,它只允许使用实现Interface_One和Interface_Two两个接口的类型,考虑应该如何实现?

scala 复制代码
//文件路径 ../08-泛型函数/08-泛型约束中使用多重类型01.ts

//定义接口:Interface_One和Interface_Two
interface Interface_One{
  func_One();
}

interface Interface_Two{
  func_Two();
}

//泛型类(泛型约束为Interface_One,Interface_Two)
class  classTest<T extends Interface_One,Interface_Two>
{
  propertyDemo:T;
  propertyDemoFunc(){
    this.propertyDemo.func_One();
    this.propertyDemo.func_Two();
  }
}

我们可能会像这样来定义泛型约束,然而上面的代码在编译的时候会抛出错误,也就是说我们不能在定义泛型约束的时候指定多个类型(上面的代码中我们指定了Interface_One和Interface_Two两个类型),如果确实需要设计多重类型约束的泛型,可以通过把多重类型的接口转换为一个超接口来处理,下面给出示例代码。

javascript 复制代码
//文件路径 ../08-泛型函数/09-泛型约束中使用多重类型02.ts

//定义接口:Interface_One和Interface_Two
interface Interface_One{
  func_One();
}

interface Interface_Two{
  func_Two();
}

//Interface_One和Interface_Two成为了超接口,它们是Interface_T的父接口
interface Interface_T extends Interface_One,Interface_Two{};

//泛型类
class  classTest<T extends Interface_T>
{
  propertyDemo:T;
  propertyDemoFunc(){
    this.propertyDemo.func_One();
    this.propertyDemo.func_Two();
  }
}

let obj = {
  func_One:function(){
    console.log("func_One");
  },
  func_Two:function(){
    console.log("func_Two");
  }
}
//获取实例化对象classTestA
let classTestA = new classTest();
classTestA.propertyDemo = obj;
classTestA.propertyDemoFunc();    //func_One func_Two


//下面是错误的演示
let classTestB = new classTest();

//报错: Type '{ func_Two: () => void; }' is not assignable to type 'Interface_T'.
classTestA.propertyDemo = {
  func_Two:function(){
      console.log("func_Two_XXXX");
  }
};

备注:该文章所有的示例代码均可以点击在Github托管仓库获取

相关推荐
崔庆才丨静觅38 分钟前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60611 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了2 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅2 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅2 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅2 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment2 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅3 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊3 小时前
jwt介绍
前端
爱敲代码的小鱼3 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax