联合类型和类型保护
联合类型展示
所谓联合类型,可以认为一个变量可能有两种或两种以上的类型。我们使用了联合类型,关键符号是|
(竖线)。
interface Waiter {
anjiao: boolean;
say: () => {};
}
interface Teacher {
anjiao: boolean;
skill: () => {};
}
function judgeWho(animal: Waiter | Teacher) {
animal.say();
}
但这时候问题来了,如果我直接写一个这样的方法,就会报错,因为judgeWho
不能准确的判断联合类型具体的实例是什么。
这时候就需要再引出一个概念叫做类型保护
,类型保护有很多种方法,这节讲几个最常使用的。
类型保护-类型断言
是使用关键词as
interface Waiter {
anjiao: boolean;
say: () => {};
}
interface Teacher {
anjiao: boolean;
skill: () => {};
}
function judgeWho(animal: Waiter | Teacher) {
if (animal.anjiao) {
(animal as Teacher).skill();
}else{
(animal as Waiter).say();
}
}
类型保护-in 语法
我们还经常使用in
语法来作类型保护,比如用if
来判断animal
里有没有skill()
方法。
function judgeWhoTwo(animal: Waiter | Teacher) {
if ("skill" in animal) {
animal.skill();
} else {
animal.say();
}
}
这里的else
部分能够自动判断
,得益于TypeScript的自动判断。
类型保护-typeof 语法
先来写一个新的add
方法,方法接收两个参数,这两个参数可以是数字number
也可以是字符串string
,如果我们不做任何的类型保护,只是相加,这时候就会报错。代码如下:
function add(first: string | number, second: string | number) {
return first + second;
}
解决这个问题,就可以直接使用typeof
来进行解决。
function add(first: string | number, second: string | number) {
if (typeof first === "string" || typeof second === "string") {
return `${first}${second}`;
}
return first + second;
}
类型保护-instanceof 语法
比如现在要作类型保护的是一个对象,这时候就可以使用instanceof
语法来作。现在先写一个NumberObj
的类,代码如下:
class NumberObj {
count: number;
}
然后我们再写一个addObj
的方法,这时候传递过来的参数,可以是任意的object
,也可以是NumberObj
的实例,然后我们返回相加值,当然不进行类型保护,这段代码一定是错误的。
function addObj(first: object | NumberObj, second: object | NumberObj) {
return first.count + second.count;
}
报错不要紧,直接使用instanceof
语法进行判断一下,就可以解决问题。
function addObj(first: object | NumberObj, second: object | NumberObj) {
if (first instanceof NumberObj && second instanceof NumberObj) {
return first.count + second.count;
}
return 0;
}
Enum 枚举类型讲解
思考引例
通过掷色子随机选择一项服务,进行程序化模拟。这里我先用 JavaScript 的写法来编写。
function getServe(status: number) {
if (status === 0) {
return "massage";
} else if (status === 1) {
return "SPA";
} else if (status === 2) {
return "dabaojian";
}
}
const result = getServe(0);
console.log(`我要去${result}`);
中级程序员写法:
const Status = {
MASSAGE: 0,
SPA: 1,
DABAOJIAN: 2,
};
function getServe(status: any) {
if (status === Status.MASSAGE) {
return "massage";
} else if (status === Status.SPA) {
return "spa";
} else if (status === Status.DABAOJIAN) {
return "dabaojian";
}
}
const result = getServe(Status.SPA);
console.log(`我要去${result}`);
高级程序员写法:
enum Status {
MASSAGE,
SPA,
DABAOJIAN,
}
function getServe(status: any) {
if (status === Status.MASSAGE) {
return "massage";
} else if (status === Status.SPA) {
return "spa";
} else if (status === Status.DABAOJIAN) {
return "dabaojian";
}
}
const result = getServe(Status.SPA);
console.log(`我要去${result}`);
这时候我们就引出了今天的主角枚举Enum
。
枚举类型的对应值
你调用时传一个1
,也会输出我要去spa
。
const result = getServe(1);
这看起来很神奇,这是因为枚举类型是有对应的数字值的,默认是从 0
开始的。我们直接用console.log()
就可以看出来了。
console.log(Status.MASSAGE); // 0
console.log(Status.SPA); // 1
console.log(Status.DABAOJIAN); // 2
可以看出结果就是0,1,2
。那这时候不想默认从 0
开始,而是想从 1
开始。可以这样写。
enum Status {
MASSAGE = 1,
SPA,
DABAOJIAN,
}
TypeScript 函数泛型-难点
现在一个简单的join
方法,方法接受两个参数``first和second
,参数有可能是字符串类型,也有可能是数字类型。方法里为了保证都可以使用,所以我们只作了字符串的基本拼接。
function join(first: string | number, second: string | number) {
return `${first}${second}`;
}
join("jspang", ".com");
这个方法现在没有任何问题,但现在有这样一个需求,就是first
参数如果传的是字符串
类型,要求second
也传字符串
类型.同理,如果是number
类型,就都是number
类型。
那现在所学的知识就完成不了啦,所以需要学习泛型
来解决这个问题。
初始泛型概念-generic
泛型的定义使用<>
(尖角号)进行定义的,比如现在给join
方法一个泛型,名字就叫做JSPang
(起这个名字的意思,就是你可以随便起一个名字,但工作中要进行语义化。),后边的参数,这时候他也使用刚定义的泛型名称。然后在正式调用这个方法时,就需要具体指明泛型的类型啦。
function join<JSPang>(first: JSPang, second: JSPang) {
return `${first}${second}`;
}
join < string > ("jspang", ".com");
这就是最简单的泛型理解,因为在实际开发中,有很多对象和类的情况,里边的具体类型我们没办法了解,所以提供了这种泛型的概念。
泛型中数组的使用
如果传递过来的值要求是数字,如何用泛型进行定义那?两种方法,第一种是直接使用[]
,第二种是使用Array<泛型>
。形式不一样,其他的都一样。
第一种写法:
function myFun<T>(params: T[]) {
return params;
}
myFun < string > ["123", "456"];
第二种写法:
function myFun<T>(params: Array<T>) {
return params;
}
myFun < string > ["123", "456"];
在工作中,我们经常使用<T>
来作泛型的表示,当然这不是硬性的规定,只是大部分程序员的习惯性写法。
多个泛型的定义
一个函数只能定义一个泛型吗?当然不是,是可以定义多个的,这里还是拿join
方法举例,定义多个泛型,比如第一个泛型用T
,第二个用P
代表。
function join<T, P>(first: T, second: P) {
return `${first}${second}`;
}
join < number, string > (1, "2");
TypeScript 类中泛型-难点
编写一个基本类
为了下面的教学演示,所以我先编写一个基本的类SelectGirl
,在类的构造函数中(constructor)
需要传递一组女孩的名称,然后再通过下边展现女孩的名称,代码如下:
class SelectGirl {
constructor(private girls: string[]) {}
getGirl(index: number): string {
return this.girls[index];
}
}
const selectGirl = new SelectGirl(["大脚", "刘英", "晓红"]);
console.log(selectGirl.getGirl(1));
这时候代码并不报错,也使用了泛型,但是在实例化对象的时候,TypeScript 是通过类型推断出来的。上节课已经介绍,这种方法并不好,所以还是需要在实例化对象的时候,对泛型的值进行确定,比如是string
类型,就这样写。
const selectGirl = new SelectGirl() < string > ["大脚", "刘英", "晓红"];
泛型中的继承
现在需求又变了,要求返回是一个对象中的name
,也就是下面的代码要改成这个样子。
interface Girl {
name: string;
}
有了接口后用extends
关键字实现泛型继承,代码如下:
class SelectGirl<T extends Girl> {
...
}
这句代码的意思是泛型里必须有一个name
属性,因为它继承了Girl
接口。
现在程序还是报错的,因为我们getGirl
方法的返回类型还不对,这时候应该是一个string
类型才对,所以代码应该改为下面的样子:
interface Girl {
name: string;
}
class SelectGirl<T extends Girl> {
constructor(private girls: T[]) {}
getGirl(index: number): string {
return this.girls[index].name;
}
}
const selectGirl = new SelectGirl([
{ name: "大脚" },
{ name: "刘英" },
{ name: "晓红" },
]);
console.log(selectGirl.getGirl(1));
我们回过头来看一下这段代码的意思,就是我们在SelectGirl
类中使用了泛型,意思是我不知道我以后要用什么类型,但是我有一个约束条件,这个类型,必须要有一个name
属性。
泛型约束
现在的泛型可以是任意类型,可以是对象、字符串、布尔、数字都是可以的。但你现在要求这个泛型必须是string
或者number
类型。这时候还是可以使用关键字extends来进行约束,把代码改成下面的样子。
class SelectGirl<T extends number | string> {
//.....
}