【TypeScript】从零开始玩转TypeScript - TypeScript中的高级类型

前言

小伙伴们大家好。今天继续来学习TypeScript。前面有一篇文章中我们已经学习了TypeScript中的基础数据类型,其实在TypeScript中还有一种数据类型叫做高级类型。下面我们就来分析一下,看看TypeScript中都有哪些高级类型,这些所谓的高级类型都长的啥样,具体如何使用。

高级类型

  • 交叉类型

所谓的交叉类型其实就是类型的合并:

  • 将多个类型合并成一个类型,合并后的类型将包含所有合并前的类型的全部成员和特性。
  • 交叉类型一般用于合并接口或类等这类引用数据类型,而不适用于我们前面学习过的基础类型
  • 交叉类型使用时是用 **&**符号将多个类型连接在一起

下面看一下代码示例,假设有一个包含姓名,年龄等属性的Person 类和一个包含说话,跑步等方法的IPerson接口,我们来利用这两个类型来实现一个交叉类型

class Person{
    
    
	name:string
	age:number
	sex:string
}

interface IPerson{
    
    
	say(world:string):string;
	run(km:number):number;
}

//per是一个包含了Person和IPerson的交叉类型
let per: Person & IPerson;
//在给per赋值时,per必须要包含Person类和IPerson接口中的所有成员,缺一或多一都不可以
per = {
    
    
	name:'Yannis', 
	age:28, 
	sex:'nv',
	say(world:string):string{
    
    
		return world
	},
	run(km:number):number{
    
    
		return km;
	}
}

// 交叉类型不适用与基础数据类型,比如定义一个string和number组合的交叉类型,定义时是被允许的,但是合并后的类型却是一个never类型,是无法进行赋值的,如下
let obj : string & number;
obj = 'hello'
obj = 1
//以上两种赋值方式都是错误的,因为没有一个值既是string类型又是number类型
  • 联合类型

上面说的交叉类型是将多个类型合并为一个类型,合并后的类型会包含所有类型的成员和特性。而下面我们要说的联合类型跟交叉类型有点类似也是与多个类型相关,但不同的是联合类型适合所有类型(包括前面学过的基础类型)。

  • 联合类型是取多个类型中的一个,也就是说只要符合多个类型的其中一个即可(类型之间是“或”的关系)
  • 联合类型适用于所有的类型,包括基础类型
  • 联合类型是多个类型之间用 “|” 分隔 ,如 let obj: string | number;
  • 如果联合类型中的每个类型都是基础类型,那么赋值时只要符合其中一个即可
  • 如果联合类型中的每个类型都是非基础类型(如类或者接口),那么我们在使用联合属性的成员时,只能访问此联合类型的所有类型的共有成员。

假设有这么一个需求:要求定义一个函数,该函数只接收一个参数,但参数的类型可以是string类型也可以是number类型,这种情况我们就可以使用联合类型了

//联合类型中的每个类型都是基础类型
function getValue(value:string | number):string|number{
    
    
	return value;
}

getValue(3); // 3
getValue("Hello");// 'Hello'
getValue(true);// 报错,类型不匹配

//联合类型中的每个类型都是非基础类型
interface IBird{
    
    
	fly();
	layEggs:number;
}

interface IFish{
    
    
	swim();
	layEggs:number;
}
let animal: IBird | IFish;
animal.layEggs;//不会报错,因为layEggs是IBird和IFish的共有属性
animal.swim();//报错:Property 'swim' does not exists on type IBird | IFish, Property 'swim' does not exists on type IBird
animal.fly();//报错:Property 'fly' does not exists on type IBird | IFish, Property 'fly' does not exists on type IFish

在上面的代码中我们发现,不管是访问swim还是fly都会报错,那么既然能这么定义为啥不能这么访问呢,必须只能访问共有成员才行吗。肯定不是这样,肯定是有办法能够实现的,就比如说我们前面学到的类型断言,我们可以通过使用类型断言来实现对非共有成员的访问,看下面代码:

interface IBird{
    
    
	fly();
	layEggs:number;
}

interface IFish{
    
    
	swim();
	layEggs:number;
}
let animal: IBird | IFish;
animal.layEggs;//共有成员没有任何问题
//使用类型断言访问非共有成员
if((<IBird>animal).fly){
    
    //推断animal是IBird类型
	(<IBird>animal).fly()
}else{
    
    
	(<IFish>animal).swim()
}

这样虽然实现了我们想要的需求,但是每次都要使用类型断言显然是有点不太方便,那还有没有其它实现呢?答案是肯定的,为了既能够访问swim也能访问fly,TypeScript为我们提供了一种类型保护机制让我们既能够访问联合类型中的公共成员也能访问非公共成员。下面我们来看下TypeScript中的类型保护机制是如何实现的。

类型保护机制

  • 自定义类型保护

TypeScript中的类型保护就是一些表达式,它们会在运行时检查从而来确保某个作用域里的类型。要实现一个类型保护也很简单,我们只需要定义一个函数,然后让该函数的返回值是一个 “类型谓词” 即可。这里又提到了一个新的概念:“类型谓词”,那么类型谓词又是什么东东?所谓的类型谓词就是:【参数 is 类型】的形式,比如: animal is Fish。下面我们就再用类型保护机制来实现一下上面的需求。

//使用类型保护机制,首先需要定义一个函数,函数的返回值类型是一个类型谓词
function isFish(animal: IFish | IBird): animal is IFish{
    
    //我们假设animal就是IFish类型
	return (<IFish>animal).swim !== undefined;
}
if(isFish(animal)){
    
    
	animal.swim();
}else{
    
    
	animal.fly();
}

在上面示例代码中,animal is IFish就是类型谓词。每当使用一些变量调用isFish时,TypeScript就会将变量缩减为那个具体的类型(本案例中为IFish)。在上面isFish方法中,typescript不仅知道if分支里的animal变量是IFish类型,它还知道在else分支里animal一定不是IFish类型,而一定是IBird类型。

  • typeof类型保护

除了使用自定义类型保护外,我们还可以使用TypeScript自带的typeof类型保护,我们知道typeof在javascript中是用来检测数据类型的,那么在typescript中也是一样我们可以通过typeof来检测联合类型的具体类型。但是typeof只能检测“number”、“string”、“boolean”和“symbol”这几种类型,而不适用与引用类型等。回到上面getValue函数的那个例子,加入现在需求稍做改动:要求在函数中根据传进来的参数的不同类型做不同的逻辑处理,这个时候我们就可以使用typeof类型保护机制来处理。

function getValue(value:string | number):void{
    
    
	if(typeof value === 'string'){
    
    
		// do somthing for string
	}else{
    
    
		// do somthing for number
	}
}
  • instanceof类型保护

上面我们提到typescript为我们提供了typeof类型保护,让我们可以不用定义函数直接进行类型的检测,但是typeof类型保护是有局限性的,就是它只能检测有限的几种基础类型,那对于一些应用类型怎么办呢?别急,同样typescript还为我们提供了另一种类型保护机制 - instanceof类型保护。与javascript中的instanceof一样,在typescript中我们可以先用instanceof来检测所使用的变量是否是某个类型的实例,如果是那也就可以访问该类型的所有成员了。

interface IBird{
    
    
	fly();
	layEggs:number;
}

interface IFish{
    
    
	swim();
	layEggs:number;
}
let animal: IBird | IFish;
animal.layEggs;//共有成员没有任何问题
//使用类型断言访问非共有成员
if(animal instanceof IBird){
    
    //推断animal是IBird类型
	animal.fly()
}else{
    
    
	animal.swim()
}

类型别名

在typescript中还为我们提供了一种新的语法 - 类型别名。所谓的类型别名,其实就是给类型取一个新的名字,让我们更方便使用。

  • 使用type关键字来定义类型别名
  • 类型别名可以用于任何类型,如:基础类型,引用类型,交叉类型和联合类型等等
  • 类的别名不会新建一个类型,它只是创建了一个名字来引用那个类型。
  • 类型别名同样也支持泛型

另外:类型别名虽然可以用于基础类型,但是给基础类型取一个别名仿佛并没有什么实际意义,类型别名还是多用于一些复杂类型,比如我们上面学过的交叉类型和联合类型等。

type Name = string; //并没有实际意义
type StringOrNumer = string | number;
type funType = () => string;

type Container<T> = {
    
    value:T};
type Tree<T> = {
    
    
	value:T
	left:Tree<T>
	right:Tree<T>
}

总结

本文我们又学习了一些新的知识点 - 高级类型,原来typescript中不仅有基础类型,还有这么多的高级类型,而这些高级类型在类库的封装也是最常用的。回顾一下,本文我们学习了交叉类型、联合类型、类型的保护机制以及类型的别名。感兴趣的小伙伴可以继续深入研究一下,本次分享就到这里了。

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

Guess you like

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