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

❏ 泛型函数类型

❏ 泛型接口(Interface)

❏ 泛型类(Class)

❏ 泛型约束

一、泛型函数的类型

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

//文件路径 ../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

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

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,下面给出示例代码。

//文件路径 ../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)抽取成为整个接口的参数,好处是抽取后我们能够清楚的知道使用的具体泛型类型是什么,且接口中的其它成员也能使用。当我们使用泛型接口的时候,传入一个类型参数来指定泛型类型即可,下面给出调整后的示例代码。

//文件路径 ../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身上,具体的使用方式和接口差不多。

//文件路径 ../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属性。

//文件路径 ../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关键字来实现泛型约束,代码如下:

//文件路径 ../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两个接口的类型,考虑应该如何实现?

//文件路径 ../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两个类型),如果确实需要设计多重类型约束的泛型,可以通过把多重类型的接口转换为一个超接口来处理,下面给出示例代码。

//文件路径 ../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托管仓库获取

前端开发系列045-基础篇之TypeScript语言特性(五)的更多相关文章

  1. 从0到1用react+antd+redux搭建一个开箱即用的企业级管理后台系列(基础篇)

    背景 ​ 最近因为要做一个新的管理后台项目,新公司大部分是用vue写的,技术栈这块也是想切到react上面来,所以,这次从0到1重新搭建一个react项目架子,需要考虑的东西的很多,包括目录结构.代码 ...

  2. 前端开发:css基础知识之盒模型以及浮动布局。

    前端开发:css基础知识之盒模型以及浮动布局 前言 楼主的蛮多朋友最近都在学习html5,他们都会问到同一个问题 浮动是什么东西?  为什么这个浮动没有效果?  这个问题楼主已经回答了n遍.今天则是把 ...

  3. ESP8266开发之旅 基础篇① 走进ESP8266的世界

    授人以鱼不如授人以渔,目的不是为了教会你具体项目开发,而是学会学习的能力.希望大家分享给你周边需要的朋友或者同学,说不定大神成长之路有博哥的奠基石... QQ技术互动交流群:ESP8266&3 ...

  4. ESP8266开发之旅 基础篇② 如何安装ESP8266的Arduino开发环境

    授人以鱼不如授人以渔,目的不是为了教会你具体项目开发,而是学会学习的能力.希望大家分享给你周边需要的朋友或者同学,说不定大神成长之路有博哥的奠基石... QQ技术互动交流群:ESP8266&3 ...

  5. ESP8266开发之旅 基础篇③ ESP8266与Arduino的开发说明

    授人以鱼不如授人以渔,目的不是为了教会你具体项目开发,而是学会学习的能力.希望大家分享给你周边需要的朋友或者同学,说不定大神成长之路有博哥的奠基石... QQ技术互动交流群:ESP8266&3 ...

  6. openlayers5-webpack 入门开发系列一初探篇(附源码下载)

    前言 openlayers5-webpack 入门开发系列环境知识点了解: node 安装包下载webpack 打包管理工具需要依赖 node 环境,所以 node 安装包必须安装,上面链接是官网下载 ...

  7. leaflet-webpack 入门开发系列一初探篇(附源码下载)

    前言 leaflet-webpack 入门开发系列环境知识点了解: node 安装包下载webpack 打包管理工具需要依赖 node 环境,所以 node 安装包必须安装,上面链接是官网下载地址 w ...

  8. 【Windows10 IoT开发系列】配置篇

    原文:[Windows10 IoT开发系列]配置篇 Windows10 For IoT是Windows 10家族的一个新星,其针对不同平台拥有不同的版本.而其最重要的一个版本是运行在Raspberry ...

  9. ESP8266开发之旅 基础篇④ ESP8266与EEPROM

    授人以鱼不如授人以渔,目的不是为了教会你具体项目开发,而是学会学习的能力.希望大家分享给你周边需要的朋友或者同学,说不定大神成长之路有博哥的奠基石... QQ技术互动交流群:ESP8266&3 ...

  10. ESP8266开发之旅 基础篇⑥ Ticker——ESP8266定时库

    授人以鱼不如授人以渔,目的不是为了教会你具体项目开发,而是学会学习的能力.希望大家分享给你周边需要的朋友或者同学,说不定大神成长之路有博哥的奠基石... QQ技术互动交流群:ESP8266&3 ...

随机推荐

  1. kettle介绍-参数变量

    ETL中为什么使用参数变量 实现ETL的复用 D,Q,P环境不同,使用变量方便发布 有的条件需要外部传入 增量ETL 灵活性强 kettle中参数变量种类 Environment Variables ...

  2. 【命令详解001】top

    top命令可以用于实时监控cpu的状态,显示系统中各个进程的资源占用情况. 本次来详细看下top命令. 常用命令示例: top # 对,无参数的top命令是最长用的资源监控命令. [root@VM_0 ...

  3. 1.3K star!像拿快递一样传送文件,这么酷!

    嗨,大家好,我是小华同学,关注我们获得 "最新.最全.最优质" 开源项目和高效工作学习方法 trzsz 是一款革命性的文件传输工具,专为终端用户设计.它完美兼容传统 rz/sz 协 ...

  4. 『Plotly实战指南』--在金融数据可视化中的应用(下)

    在金融市场的复杂博弈中,可视化技术如同精密的导航仪. 传统静态图表正在被交互式可视化取代--据Gartner研究,采用动态可视化的投资机构决策效率提升达47%. 本文的目标是探讨如何利用 Plotly ...

  5. Flex布局-容器项

    弹性盒子是一种用于 按行 或 按列的一维布局方法. 元素可以膨胀以填充额外的空间, 也可以 收缩 以适应更小的空间. flex 重点概览 对于 flex 重要的理解点在于: 主轴与交叉轴 换行与缩写 ...

  6. vue3 基础-样式绑定语法

    本篇讲 vue 通过数据去进行 dom 样式的绑定操作, 主要分为 字符串, 数组, 对象等方式, 这个非常好理解, 凭着我们朴素的情感就能一步领悟到位的, 就还是演示一段吧. 字符 & 数组 ...

  7. 解析V8引擎底层原理,探究其优异性能之谜

    @charset "UTF-8"; .markdown-body { line-height: 1.75; font-weight: 400; font-size: 15px; o ...

  8. DeepSeek+Coze实战:从0到1搭建小红书图文改写智能体(喂饭级教程)

    大家好,我是汤师爷,专注AI智能体分享~ 还在为小红书笔记创作发愁吗? 每天都要绞尽脑汁想文案,看着别人的爆款笔记却不知道如何模仿? 今天,我就教你如何利用AI智能体,轻松实现小红书图文改写,让创作效 ...

  9. C#之可访问性约束(可访问性不一致)

    C# 语言中的有些构造要求某个类型至少与某个成员或其他类型具有同样的可访问性 (at least as accessible as).如果 T 的可访问域是 M 可访问域的超集,我们就说类型 T 至少 ...

  10. AD 侦查-AS-REP 烘烤攻击

    本文通过 Google 翻译 AD Recon – AS-REP Roasting Attacks 这篇文章所产生,本人仅是对机器翻译中部分表达别扭的字词进行了校正及个别注释补充. 导航 0 前言 1 ...