【TypeScript】从零开始玩转TypeScript - TypeScript中的泛型

前言

小伙伴们大家好。我们继续来学习TypeScript。今天要给大家分享的是TypeScript中的泛型。不知道小伙伴们有没有了解泛型的。先来看这么一种场景:假设有一个函数,该函数只接收一个参数,现要求函数的返回值类型必须与参数的类型一致,并且该函数的参数可接收任意类型的参数。想想该如何定义该函数呢。我们先来看看下面的代码能不能实现

//使用上篇文章学到的重载函数
function add(x:number):number;
function add(x:string):string;
function add(x:boolean):boolean;
function add(x:number[]):number[];
function add(x){
    
    return x}

// 参数类型和返回值类型都使用any\
function add2(x:any):any{
    
    }

上面两个方法中

  • 使用重载函数能够满足参数类型和返回值类型一致,但是不满足第二个要求:“能够接收任意类型的参数”,也就是说只能满足定义了的参数类型。所以这个方法不能满足需求
  • 第二个方法参数类型和返回值类型都是any,这可以接收任意类型的参数,但无法保证返回值类型和参数类型一致。所以这个方法也是不行的。
    如果你了解泛型,那么这个需求就很容易实现了。接下来让我们看看TypeScript中什么是泛型,如何使用泛型吧!

泛型

  • 泛型的定义:
    泛型是程序设计语言的一种风格和规范。泛型运行我们在强类型程序设计语言中编写代码时使用一些以后才指定的类型,在使用时作为参数指明这些类型。也就是说泛型在定义时并不知道具体是什么类型,而是在使用时由开发者来决定(在使用的时候才知道是什么类型),这样定义一个泛型就可以支持任何类型了。而我们之前学过的变量定义,是在定义时就已经确定了变量的类型并且是不能改变的。这也是泛型的优点所在。
  • 泛型的使用
    在TypeScript中有一种特殊的变量叫做类型变量,这种变量只用于表示一个类型而不是具体的值。一般情况使用大写字母 T 来表示。泛型可用于函数、接口、类等对象的定义,定义泛型的语法也很简单就是:对象名< T > 的形式。了解了泛型之后我们再来看下前言中提到的那个需求是不是就迎刃而解了呢。
function add<T>(arg:T):T{
    
    
	return arg
}
//在调用时可以显示的指明T的具体类型,也可以不指定让程序通过类型推断来判断类型
// 正确的使用泛型
add<number>(1);//显示指定
add("hello") // 类型推断

// 错误的使用
add<string>(1);// 报错:类型不匹配

我们在使用泛型函数的时候需要注意一点:这个T代表的是一个通用类型,而不是一个具体的类型,也就是说我们必须把这种类型的参数当做是一个任意类型,因此有些属性是不能随便使用的,比如我们在上面的函数体中加了这样一句代码:

console.log(arg.length)

这个时候编译器就会抛出一个错误,因为T是不确定类型,不一定有length属性,比如我们使用时传了一个number类型,那么它是没有长度属性的,因此就会报错。

泛型函数类型与泛型接口

在上一篇文章我们学习函数的时候提到了函数可以作为变量的数据类型使用,那么同样泛型函数也可以作为变量的类型使用,我们称之为泛型函数类型。泛型函数类型和普通函数类型的定义和使用基本没有太大的区别,唯一的不同就是泛型函数类型需要在最前面加一个类型参数,如下

let add: <T>(arg:T)=> T;
add = function <T>(arg:T):T{
    
    
	return arg;
}

上面的代码,我们还可以写成接口的形式,如下

//先定义一个接口
interface IAdd{
    
    
	<T>(arg:T):T
}

let add:IAdd = function <T>(arg:T):T{
    
    
	return arg;
}

上面代码通过接口形式同样可以实现一个泛型函数类型,但有时候一个接口中可能并不只有一个成员,并且每个成员对应的类型也要与接口类型一致。这就引出了泛型接口。

泛型接口是将泛型参数直接定义在接口上,作为整个接口的一个参数,这样我们就能够很清楚的知道使用的是具体哪个泛型类型,同样接口中的所有成员也能知道这个参数类型了,比如我们继续把上面的代码改造一下:

//定义一个泛型接口
interface IAdd<T>{
    
    
	(arg:T):T
}

//string类型的泛型
let add:IAdd<string> = function <T>(arg:T):T{
    
    
	return arg;
}

泛型类

泛型类的定义: 除了泛型接口,我们还可以定义一个泛型类。泛型类的定义跟泛型接口差不多,也是将 < T >放在类名后面,这样就实现了一个泛型类的定义。

**泛型类的使用:**泛型类的使用跟普通类是一样的,通过关键字new来创建一个类的实例,不同的是泛型类在创建实例是需要传递一个具体的类型,从而告诉我们具体使用的是什么类型的泛型

我们在前面学习类的时候有提到:类可分为两部分:静态部分和实例部分,泛型类指的是实例部分的类型,而类的静态部分是不能使用泛型类型的。

class Add<T>{
    
    
	result:T;
	sum(x:T, y:T):T;
}

let add = new Add<number>();
add.result = 0;
add.sum = function(x,y){
    
    return x+y}

let hello = new Add<string>();
hello.result = 'hello'
hello.sum = function(x,y){
    
    return x + y}

这里我们在定义的时候并没有指定具体的类型,而是在使用时由开发者决定使用什么类型,可以是number、string、boolean等等。这就是泛型给我们带来的好处所在。

泛型的约束

学习完泛型接口和泛型类,下面我们再来看下泛型约束。在前面的一个示例中我们想在泛型函数中使用泛型类型的length属性时会报错,那如果我们就是想使用这个length属性咋办呢,这个时候就可以借助泛型约束来实现了。
比如我们先声明一个接口,并在接口中定义一个number类型的length属性,然后再用这个接口和关键字extends来对泛型进行约束。也就是说虽然我们定义的是一个泛型,但是在具体使用的时候所传递的类型要必须符合约束才行,在本案例中根据约束:我们要传递的值必须是包含length属性的。看下面代码实现:

interface ILength{
    
    
	length:number;
}

function add<T extends ILength>(arg:T):T{
    
    
	console.log(arg.length);
}

add(3);//报错 数字3不符合泛型约束
add("hello world");// 正确,因为字符串本身就有length属性的
add({
    
    length:20, value:3}); // 带length属性的对象也是符合泛型约束的

总结

本文我们又get到了一个新的知识点 - 泛型,通过对本文的学习我们了解了泛型的定义以及泛型的用途,其实本文中只是泛型的冰山一角,在日常开发中如果我们使用的一些第三方库会大量的使用各种泛型,本文只是作为泛型的入门,想深入了解泛型的小伙伴可以多去看看别人代码,好了本文就分享到这里了。

喜欢的小伙伴欢迎点赞留言加关注哦!

Guess you like

Origin blog.csdn.net/lixiaosenlin/article/details/121230596