Ts初识5

266 阅读4分钟

联合类型和类型保护

联合类型展示

所谓联合类型,可以认为一个变量可能有两种或两种以上的类型。我们使用了联合类型,关键符号是|(竖线)。

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> {
  //.....
}