文章

TypeScript 入门

入门

TypeScript 入门

入门

初体验

  1. Typescript 是 JS 的超集,为 JS 添加了类型系统。相比 JS 开发体验更友好,提前发现错误, BUG更少,增加开发的幸福度。

安装解析TS工具包

1
npm i typescript -g

简化执行TS步骤

1
npm i ts-node -g

1. TypeScript静态类型

1.1 如何定义静态类型

1
2
let age: number = 18;
let name: string = '周杰伦';

1.2 自定义静态类型

1
2
3
4
5
6
7
8
9
interface singer {
  name: string;
  age: number;
}
const jieLun: singer = {
  name: '周杰伦',
  age: 18
}
console.log(jieLun); //{ name: '周杰伦', age: 18 }

2. TypeScript基础静态类型和对象类型

2.1 基础静态类型

1
2
let age: number = 18;
let name: string = '周杰伦';

null、undefinde、symbol、boolean、void 这些都是最常用的基础数据类型

2.2 对象类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//对象类型
const singer: {
  name: string,
  age: number,
} = {
  name: '周杰伦',
  age: 18,
}
console.log(singer); //{ name: '周杰伦', age: 18 }

//数组类型
const singers: string [] = ['周杰伦', '许嵩', '张杰'];
console.log(singers); //[ '周杰伦', '许嵩', '张杰' ]

//类类型
class Person{}
const jieLun: Person = new Person();

//函数类型
const xuSong: ()=>string = () => {
  return '许嵩';
};
console.log(xuSong()); //许嵩

3.Typescript中的类型注解和类型判断

3.1 type annotation 类型注解

1
2
let count: number;
count = 123;

这段代码就是类型注解,意思是显示的告诉代码,我们的count变量就是一个数字类型,这就叫做类型注解。

3.2 type inferrence 类型判断

1
let countInference = 123;

这时候我并没有显示的告诉你变量countInference是一个数字类型,但是如果你把鼠标放到变量上时,你会发现 TypeScript 自动把变量注释为了number(数字)类型,也就是说它是有某种推断能力的,通过你的代码 TS 会自动的去尝试分析变量的类型。

3.3 工作中使用

  • 如果TS能够自动分析变量类型, 我们就什么也不需要做了
  • 如果TS无法分析变量类型的话, 我们就需要使用类型注解

先来看一个不用写类型注解的例子:

1
2
3
const one = 1;
const two = 2;
const three = one + two;

再来看一个用写类型注解的例子:

1
2
3
4
5
function getTotal(one, two) {
  return one + two;
}

const total = getTotal(1, 2);

这种形式,就需要用到类型注释了,因为这里的 one 和 two 会显示为 any 类型。这时候如果你传字符串,你的业务逻辑就是错误的,所以你必须加一个类型注解,把上面的代码写成下面的样子。

1
2
3
4
5
function getTotal(one: number, two: number) {
  return one + two;
}

const total = getTotal(1, 2);

这里有的一个问题是,为什么 total 这个变量不需要加类型注解,因为当 one 和 two 两个变量加上注解后,TypeScript 就可以自动通过类型推断,分析出变量的类型。

当然 TypeScript 也可以推断出对象中属性的类型:

1
2
3
4
const singers = {
  name: "周杰伦",
  age: 18,
};

写完后你把鼠标放在 singers 对象上面,就会提示出他里边的属性,这表明 TypeScript 也分析出了对象的属性的类型。

在写 TypeScript 代码的一个重要宗旨就是每个变量,每个对象的属性类型都应该是固定的,如果你推断就让它推断,推断不出来的时候你要进行注释。

4. Typescript函数参数和返回类型定义

4.1 简单的类型定义

定义返回类型为 number

1
2
3
4
5
function getTotal(one: number, two: number): number {
  return one + two;
}

const total = getTotal(1, 2);

4.2 函数无返回值时的定义

有时候函数是没有返回值的,比如现在定义一个 sayHello 的函数,这个函数只是简单的 terminal 打印,并没有返回值。我们就可以给他一个类型注解 void ,代表没有任何返回值。

1
2
3
function sayHello(): void {
  console.log("hello world");
}

4.3 never返回值类型

如果一个函数是永远也执行不完的,就可以定义返回值为 never

比如执行的时候,抛出了异常,这时候就无法执行完了:

1
2
3
4
function errorFuntion(): never {
  throw new Error();
  console.log("Hello World");
}

还有一种是 死循环,这样也运行不完,比如下面的代码:

1
2
3
4
function forNever(): never {
  while (true) {}
  console.log("Hello JSPang");
}

4.4 函数参数为对象(解构)时

当一个函数的参数是对象时,我们如何定义参数对象的属性类型。

1
2
3
4
5
function add({ one, two }: { one: number, two: number }): number {
  return one + two;
}

const three = add({ one: 1, two: 2 });

如果参数是对象,并且里边只有一个属性时:

1
2
3
4
5
function getNumber({ one }: { one: number }): number {
  return one;
}

const one = getNumber({ one: 1 });

5. Typescript中数组类型的定义

5.1 一般数组类型的定义

定义一个最简单的数组类型,比如数字类型:

1
const numberArr = [1, 2, 3];

这时候你把鼠标放在 numberArr 上面可以看出,这个数组的类型就是 number 类型。这是 TypeScript 通过类型推断自己推断出来的。 如果你要显示的注解,也非常简单,可以写成下面的形式。

1
const numberArr: number[] = [1, 2, 3];

数组各项是字符串:

1
const stringArr: string[] = ["a", "b", "c"];

定义任意类型的数组,比如是undefined:

1
const undefinedArr: undefined[] = [undefined, undefined];
数组中有多种类型,比如既有数字类型,又有字符串的时候,只要加个 (),然后在里边加上就可以了:
1
const arr: (number | string)[] = [1, "string", 2];

5.2 数组中对象类型的定义

定义一个歌手的数组:

1
2
3
4
const singer: { name: string, age: Number }[] = [
  { name: "周杰伦", age: 18 },
  { name: "林俊杰", age: 28 },
];

这种形式看起来比较麻烦,而且如果有同样类型的数组,写代码也比较麻烦,TypeScript 为我们准备了一个概念,叫做 类型别名(type alias)。

比如刚才的代码,就可以定义一个类型别名,定义别名的时候要以type关键字开始。现在定义一个Men的别名。

1
type Men = { name: string, age: Number };

有了这样的类型别名以后哦,就可以把上面的代码改为下面的形式了。

1
2
3
4
5
6
type Men = { name: string, age: Number };

const singer: Men[] = [
  { name: "周杰伦", age: 18 },
  { name: "林俊杰", age: 28 },
];

这样定义是完全起作用的,比如我们下面在对象里再加入一个属性,这时候编译器就会直接给我们报错了。

用类进行定义也是可以的,比如我们定义一个Gentle的类,然后用这个类来限制数组的类型也是可以的:

1
2
3
4
5
6
7
8
9
class Gentle {
  name: string;
  age: number;
}

const singer: Gentle[] = [
  { name: "周杰伦", age: 18 },
  { name: "林俊杰", age: 28 },
];

6. Typescript中元组的使用和类型约束

6.1 元组的基本使用

我们先来看一个数组和这个数组注解的缺点:

1
const singer = ["周杰伦", "林俊杰", 28];

这时候把鼠标放到 singer 变量上面,可以看出推断出来的类型。我们就用类型注解的形式给他作一个注解,代码如下:

1
const singer: (string | number)[] = ["周杰伦", "林俊杰", 28];

这时候你已经增加了代码注解,但是这并不能很好的限制,比如我们把代码改成下面的样子,TypeScript依然不会报错。

1
const singer: (string | number)[] = ["周杰伦", 28, "林俊杰"];

我们只是简单的把数组中的位置调换了一下,但是TypeScript并不能发现问题,这时候我们需要一个更强大的类型,来解决这个问题,这就是元组

元组和数组类似,但是类型注解时会不一样。

1
const singer: [string, string, number] = ["周杰伦", "林俊杰", 28];

这时候我们就把数组中的每个元素类型的位置给固定住了,这就叫做元组

6.2 元组的使用

目前我的工作中不经常使用元组,因为如果要使用元组,完全可以使用对象的形式来代替,但是如果你维护老系统,你会发现有一种数据源时CSV,这种文件提供的就是用逗号隔开的,如果要严谨的编程就需要用到元组了。例如我们有这样一组由CSV提供的(注意这里只是模拟数据)。

1
2
3
"zhoujie", "teacher", 28;
"xusong", "teacher", 18;
"zhaolei", "teacher", 25;

如果数据源得到的数据时这样的,你就可以使用元组了:

1
2
3
4
5
const singer: [string, string, number][] = [
  ["zhoujie", "teacher", 28],
  ["xusong", "teacher", 18],
  ["zhaolei", "teacher", 25],
];

7. Typescript中的 interface 接口

7.1 interface 接口初步了解

现在我们要作一个简历的自动筛选程序,年龄小于 25 岁,胸围大于 90 公分的:

1
2
3
4
5
6
const screenResume = (name: string, age: number, bust: number) => {
  age < 24 && bust >= 90 && console.log(name + "进入面试");
  age > 24 || (bust < 90 && console.log(name + "你被淘汰"));
};

screenResume("杰伦", 18, 94);

这时候又增加了需求,必须能看到这些人的简历信息,于是又写了下面这个方法:

1
2
3
4
5
6
const getResume = (name: string, age: number, bust: number) => {
  console.log(name + "年龄是:" + age);
  console.log(name + "胸围是:" + bust);
};

getResume("杰伦", 18, 94);

这时候问题来了,程序开发中一直强调“代码重用”,两个方法用的类型注解一样,需要作个统一的约束。上面的 类型别名 可以解决代码重复的问题,现在看一个更常用的语法 接口(Interface).

我们可以把这两个重复的类型注解,定义成统一的接口。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
interface Men {
  name: string;
  age: number;
  bust: number;
}

const screenResume = (men: Men) => {
  men.age < 24 && men.bust >= 90 && console.log(men.name + "进入面试");
  men.age > 24 || (men.bust < 90 && console.log(men.name + "你被淘汰"));
};

const getResume = (men: Men) => {
  console.log(men.name + "年龄是:" + men.age);
  console.log(men.name + "胸围是:" + men.bust);
};

const men = {
  name: "杰伦",
  age: 18,
  bust: 94,
};

screenResume(men);
getResume(men);

7.2 接口和类型别名的区别

类型别名可以直接给类型,比如 string,而接口必须代表对象。

比如我们的 类型别名 可以写出下面的代码:

1
type Men = stirng;

但是接口就不能这样写,它必须代表的是一个对象,也就是说,你初始化 men 的时候,必须写出下面的形式:

1
2
3
4
5
const men = {
  name: "杰伦",
  age: 18,
  bust: 94,
};

7.3 接口非必选值的定义

比如这时候又有了新的要求,要求尽量能看到腰围,但是不作强制要求,就是可选值。在 : 号前加一个 ?

比如把 Men 的接口写成这样:

1
2
3
4
5
6
interface Men {
  name: string;
  age: number;
  bust: number;
  waistline?: number;
}

然后我们再修改一下getResume方法,写成这样:

1
2
3
4
5
const getResume = (men: Men) => {
  console.log(men.name + "年龄是:" + men.age);
  console.log(men.name + "胸围是:" + men.bust);
  men.waistline && console.log(men.name + "腰围是:" + men.waistline);
};

这时候在定义 men 对象的时候,就可以写 saistline(腰围),也可以不写了。

7.4 允许加入任意值

1
2
3
4
5
6
7
interface Men {
  name: string;
  age: number;
  bust: number;
  waistline?: number;
  [propname: string]: any;
}

这个的意思是,属性的名字是字符串类型,属性的值可以是任何类型。

这时候我们在对象里给一个性别,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const men = {
  name: "杰伦",
  age: 18,
  bust: 94,
  waistline: 21,
  sex: "",
};

const getResume = (men: Men) => {
  console.log(men.name + "年龄是:" + men.age);
  console.log(men.name + "胸围是:" + men.bust);
  men.waistline && console.log(men.name + "腰围是:" + men.waistline);
  men.sex && console.log(men.name + "性别是:" + men.sex);
};

7.5 接口里的方法

接口里不仅可以存属性,还可以存方法,比如这时候有个say()方法,返回值是string类型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
interface Men {
  name: string;
  age: number;
  bust: number;
  waistline?: number;
  [propname: string]: any;
  say(): string;
}

const men = {
  name: "杰伦",
  age: 18,
  bust: 94,
  waistline: 21,
  sex: "",
  say() {
    return "天青色等烟雨,而我在等你!";
  },
};

7.6 接口和类的约束

类可以和接口很好的结合,我们先来看一个例子:

1
class singer implements Men {}

这时候类会直接报错,所以我们需要把这个类写的完全点。

1
2
3
4
5
6
7
8
class singer implements Men {
  name = "杰伦";
  age = 18;
  bust = 90;
  say() {
    return "天青色等烟雨,而我在等你!";
  }
}

7.7 接口间的继承

接口也可以用于继承的,比如你新写一个Teacher 接口,继承于 Men 接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
interface Teacher extends Men {
  teach(): string;
}
//这时候必须传 teach 方法
const men = {
  name: "杰伦",
  age: 18,
  bust: 94,
  waistline: 21,
  sex: "",
  say() {
    return "天青色等烟雨,而我在等你!";
  },
  teach() {
    return "我是一个音乐老师";
  },
};

const getResume = (men: Teacher) => {
  console.log(men.name + "年龄是:" + men.age);
  console.log(men.name + "胸围是:" + men.bust);
  men.waistline && console.log(men.name + "腰围是:" + men.waistline);
  men.sex && console.log(men.name + "性别是:" + men.sex);
};

8.Typescript中类的概念和使用

8.1 类的基本使用

定义一个 Lady 类,这里要使用关键字 class,类里边有姓名属性和一个得到姓名的方法,代码如下:

1
2
3
4
5
6
7
8
9
class Lady {
  content = "Hi,帅哥";
  sayHello() {
    return this.content;
  }
}

const goddess = new Lady();
console.log(goddess.sayHello()); //Hi,帅哥

8.2 类的继承

这里说一下 TypeScrip 的继承和ES6中的继承是一样的。关键字也是 extends,比如我们这里新建一个 Beauty 的类,然后继承自 Lady 类,在 Beauty 类里写一个新的方法,叫做 sayLove,具体代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Lady {
  content = "Hi,帅哥";
  sayHello() {
    return this.content;
  }
}
class Beauty extends Lady {
  sayLove() {
    return "I love you";
  }
}

const goddess = new Beauty();
console.log(goddess.sayHello()); //Hi,帅哥
console.log(goddess.sayLove()); //I love you

8.3 类的重写

重写就是子类可以重新编写父类里边的代码。现在我们在 Beauty 这个类里重写父类的 sayHello() 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Lady {
  content = "Hi,帅哥";
  sayHello() {
    return this.content;
  }
}

class Beauty extends Lady {
  sayLove() {
    return "I love you";
  }
  sayHello() {
    return "Hi , 美女";
  }
}

const goddess = new Beauty();
console.log(goddess.sayLove()); //I love you
console.log(goddess.sayHello()); //Hi , 美女

8.4 super关键字

比如我们还是想使用Lady类中说的话,在后面加上你好两个字就可以了。这时候就可以使用 super 关键字,它代表父类中的方法。那我们的代码就可以写成这个样子了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Lady {
  content = "Hi,帅哥";
  sayHello() {
    return this.content;
  }
}

class Beauty extends Lady {
  sayLove() {
    return "I love you";
  }
  sayHello() {
    return super.sayHello() + "。你好!";
  }
}

const goddess = new Beauty();
console.log(goddess.sayLove()); //I love you
console.log(goddess.sayHello()); //Hi,帅哥。你好!

9 Typescript中类的访问类型

类的访问类型就是基于三个关键词 privateprotectedpublic ,也是三种访问类型。

先写一个简单的类:

1
2
3
4
5
6
7
8
class Person {
  name: string;
}

const person = new Person();
person.name = "jielun";

console.log(person.name);//jielun

9.1 public访问属性

上面可以打出 jielun 是因为我们如果不在类里对 name 的访问属性进行定义,那么它就会默认是public访问属性。

这就相当于下面的这段代码:

1
2
3
class Person {
  public name:string;
}

public 从英文字面的解释就是公共的,在程序里的意思就是允许在类的内部和外部被调用

比如我们在类内调用,我们在写一个 sayHello 的方法:

1
2
3
4
5
6
class Person {
  public name:string;
  public sayHello(){
    console.log(this.name + ' say Hello')
  }
}

这是的 this.name 就是类的内部调用;下面从注释横线下,全部是类的外部:

1
2
3
4
5
6
7
8
9
10
11
class Person {
    public name:string;
    public sayHello(){
        console.log(this.name + 'say Hello')
    }
}
//-------以下属于类的外部--------
const person = new Person()
person.name = 'jielun'
person.sayHello()
console.log(person.name)

9.2 private访问属性

private 访问属性的意思是,只允许再类的内部被调用,外部不允许调用

现在我们把 name 属性改成 private,这时候在类的内部使用不会提示错误,而外部使用直接会报错。

1
2
3
4
5
6
7
8
9
10
11
class Person {
  private name:string;
  public sayHello(){
    console.log(this.name + 'say Hello')  //此处不报错
  }
}
//-------以下属于类的外部--------
const person = new Person()
person.name = 'jielun'    //此处报错
person.sayHello()
console.log(person.name)  //此处报错

9.3 protected访问属性

protected 允许在类内继承的子类中使用

做一个例子,把 name 的访问属性换成 protected,这时候外部调用 name 的代码会报错,内部的不会报错,和 private 一样。这时候我们再写一个 Teacher 类,继承于 Person,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
class Person {
  protected name:string;
  public sayHello(){
    console.log(this.name + 'say Hello')  //此处不报错
  }
}

class Teacher extends Person{
  public sayBye(){
    this.name; //此处不报错
  }
}

9.4 只读属性readonly

下面这种情况是可以随意更改的:

1
2
3
4
5
6
7
class Person {
  constructor(public name:string ){ }
}

const person = new Person('jielun')
person.name= '杰伦'
console.log(person.name) //杰伦

现在的需求是不能更改了,也就是常说的只读属性,这时候就可以用一个关键词 readonly

1
2
3
4
5
6
7
8
9
10
class Person {
  public readonly _name :string;
  constructor(name:string ){
    this._name = name;
  }
}

const person = new Person('jielun')
person._name= '杰伦' //报错
console.log(person._name)

10 Typescript 类的构造函数

10.1 类的构造函数(构造器)

例:新建一个 Person 类,类的里边定义一个 name,但是 name 我们并不给他值,然后我们希望在 new 出对象的时候,直接通过传递参数的形式,给 name 赋值并打印出来。这时候我们就需要用到构造函数了,构造函数的关键字是 constructor

1
2
3
4
5
6
7
8
9
class Person{
  public name :string ;
  constructor(name:string){
    this.name=name
  }
}

const person= new Person('jielun')
console.log(person.name) //jielun

简单写法:

1
2
3
4
5
6
7
class Person{
  constructor(public name:string){
  }
}

const person= new Person('jielun')
console.log(person.name) //jielun

10.2 类继承中的构造器写法

在子类中使用构造函数需要用 super() 调用父类的构造函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
class Person{
  constructor(public name:string){}
}

class Teacher extends Person{
  constructor(public age:number){
    super('jielun')
  }
}

const teacher = new Teacher(18)
console.log(teacher.age) //18
console.log(teacher.name) //jielun

这就是子类继承父类并有构造函数的原则,就是在子类里写构造函数时,必须用 super() 调用父类的构造函数,如果需要传值,也必须进行传值操作。就是父类没有构造函数,子类也要使用 super() 进行调用,否则就会报错。

1
2
3
4
5
6
7
8
9
10
class Person{}

class Teacher extends Person{
  constructor(public age:number){
    super() //ES6 要求,子类的构造函数必须执行一次 super 函数,否则会报错。
  }
}

const teacher = new Teacher(18)
console.log(teacher.age) //18

11 Typescript 类的Getter、Setter和static的使用

类的访问类型 private,它的最大用处是封装一个属性,然后通过 Getter 和 Setter 的形式来访问和修改这个属性。

11.1 类的 Getter 和 Setter

声明一个 Person 类,年龄是不能随便告诉人,所以使用了 private,这样别人就都不知道她的真实年龄,而只有她自己知道:

1
2
3
class Person {
  constructor(private _age:number){}
}

如果别人想知道,就必须通过 getter 属性知道。getter 属性的关键字是 get,后边跟着类似方法的东西,但是你要注意,它并不是方法,归根到底还是属性。

1
2
3
4
5
6
7
8
9
class Person {
  constructor(private _age:number){}
  get age(){
    return this._age
  }
}

const jielun = new Person(18)
console.log(jielun.getAge) //18

这时候你会觉的这么写不是多此一举吗?玄妙就在于 getter 里,我们可以对 age进行处理,比如别人问的时候我们就偷摸的减少 10 岁:

1
2
3
4
5
6
class Person {
  constructor(private _age:number){}
  get age(){
    return this._age-10
  }
}

age是私有的,那类的外部就没办法改变,所以这时候可以用 setter 属性进行改变:

1
2
3
4
5
6
7
8
9
10
11
12
13
class Person {
  constructor(private _age:number){}
  get age(){
      return this._age-10
  }
  set age(age:number){
    this._age=age
  }
}

const jielun = new Person(28)
jielun.age=25
console.log(jielun.age) //15

可以 setter 里给他加上个 3 岁:

1
2
3
set age(age:number){
  this._age=age+3
}

11.2 类中的 static

常规写法:

1
2
3
4
5
6
7
8
class Girl {
  sayLove() {
    return "I Love you";
  }
}

const girl = new Girl();
console.log(girl.sayLove()); //I Love you

但是现在你不想new出对象,而直接使用这个方法,那TypeScript为你提供了快捷的方式,用 static 声明的属性和方法,不需要进行声明对象,就可以直接使用:

1
2
3
4
5
6
class Girl {
  static sayLove() {
    return "I Love you";
  }
}
console.log(Girl.sayLove()); //I Love you

12 抽象类

抽象类很父类很像,都需要继承,但是抽象类里一般都有抽象方法。继承抽象类的类必须实现抽象方法才可以。

通俗点:比如我开了一个科技公司,里边有Java程序员,有Web程序员,UI设计师,每一个岗位我都写成一个类,那代码就是这样的:

1
2
3
4
5
class JavaPerson {}

class WebPerson {}

class UiPerson {}

我作为老板,我要求无论是什么职位,都要有独特的技能,每个职位的技能又不同,这时候就可以用抽象类来解决问题。

抽象类的关键词是 abstract,里边的抽象方法也是 abstract 开头的,现在我们就写一个 Person 的抽象类。

1
2
3
abstract class Person{
  abstract skill()  //因为没有具体的方法,所以我们这里不写括号
}

有了这个抽象类,三个类就可以继承这个类,然后会要求必须实现 skill() 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
abstract class Person{
    abstract skill()  //因为没有具体的方法,所以我们这里不写括号

}

class JavaPerson extends Person{
  skill(){
    console.log('我会写Java!')
  }
}

class WebPerson extends Person{
  skill(){
    console.log('我会写JavaScript!')
  }
}

class UiPerson extends Person{
  skill(){
    console.log('我会PS!')
  }
}

13 配置文件 - 初识 tsconfig.json

14 配置文件 - 初识 compilerOptions 配置项

15 配置文件 - compilerOptions 配置内容详解

16 联合类型和类型保护

所谓联合类型,可以认为一个变量可能有两种或两种以上的类型。需要注意的是,只有联合类型存在的情况下,才需要类型保护。普通的类型注解,并不需要我们这种特殊操作。

16.1 简单的联合类型

1
2
3
let myFavoriteNumber: string | number;
myFavoriteNumber = 'seven';
myFavoriteNumber = 7;
联合类型使用分隔每个类型。
这里的 let myFavoriteNumber: stringnumber 的含义是,允许 myFavoriteNumber 的类型是 string 或者 number,但是不能是其他类型。

16.2 联合类型展示

例:声明两个接口 qianDuan(前端工程师接口和 houDuan(后端工程师)接口,然后在写一个 judgeWho(判断是谁)的方法,里边传入一个 animal(任意值),这时候可以能是qianDuan,也可能是houDuan。所以我们使用了联合类型,关键符号是(竖线)。
1
2
3
4
5
6
7
8
9
10
11
interface qianDuan {
  ps: boolean;
  goJs: () => {};
}

interface houDuan {
  ps: boolean;
  goPhp: () => {};
}

function judgeWho(animal: qianDuan | houDuan) {}

但这时候问题来了,如果我直接写一个这样的方法,就会报错,因为judgeWho不能准确的判断联合类型具体的实例是什么。

1
2
3
function judgeWho(animal: Waiter | Teacher) {
  animal.goJs(); //报错
}

这时候就需要再引出一个概念叫做类型保护

16.3 类型保护 - 类型断言

类型断言就是通过断言的方式确定传递过来的准确值,比如上面的程序,如果会 ps,说明他就是前端工程师,这时候就可以通过断言animal as qianDuan,然后直接调用 goJs() 方法,程序就不再报错了。同样如果不会ps,说明就是后端工程师,这时候调用 goPhp()方法,就不会报错了。这就是通过断言的方式进行类型保护。也是最常见的一种类型保护形式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
interface qianDuan {
  ps: boolean;
  goJs: () => {};
}

interface houDuan {
  ps: boolean;
  goPhp: () => {};
}

function judgeWho(animal: qianDuan | houDuan) {
  if (animal.ps) {
    (animal as qianDuan).goJs();
  }else{
    (animal as houDuan).goPhp();
  }
}

16.4 类型保护 - in 方法

我们还经常使用 in 语法来作类型保护,比如用 if 来判断 animal 里有没有 goJs() 方法:

1
2
3
4
5
6
7
function judgeWhoTwo(animal: qianDuan | houDuan) {
  if ("goJs" in animal) {
    animal.goJs();
  } else {
    animal.goPhp();
  }
}

这里的else部分能够自动判断,得益于TypeScript的自动判断。

16.5 类型保护 - typeof 方法

写一个新的 add 方法,方法接收两个参数,这两个参数可以是数字number也可以是字符串string,如果我们不做任何的类型保护,只是相加,这时候就会报错。代码如下:

1
2
3
function add(first: string | number, second: string | number) {
  return first + second;
}

解决这个问题,就可以直接使用typeof来进行解决:

1
2
3
4
5
6
function add(first: string | number, second: string | number) {
  if (typeof first === "string" || typeof second === "string") {
    return `${first}${second}`;
  }
  return first + second;
}

16.6 类型保护 - instanceof 方法

现在要作类型保护的是一个对象,这时候就可以使用instanceof语法来作。现在先写一个NumberObj的类:

1
2
3
class NumberObj {
  count: number;
}

然后我们再写一个addObj的方法,这时候传递过来的参数,可以是任意的object,也可以是NumberObj的实例,然后我们返回相加值,当然不进行类型保护,这段代码一定是错误的:

1
2
3
function addObj(first: object | NumberObj, second: object | NumberObj) {
  return first.count + second.count;
}

报错不要紧,直接使用 instanceof 语法进行判断一下,就可以解决问题:

1
2
3
4
5
6
function addObj(first: object | NumberObj, second: object | NumberObj) {
  if (first instanceof NumberObj && second instanceof NumberObj) {
    return first.count + second.count;
  }
  return 0;
}

instanceof 只能用在类上

17 Enum 枚举类型

17.1 案例

比如我现在写个程序随机选一个景点去旅游:

初级程序员写法:

1
2
3
4
5
6
7
8
9
10
11
function getTravel(status: number) {
  if (status === 0) {
    return "北京";
  } else if (status === 1) {
    return "上海";
  } else if (status === 2) {
    return "杭州";
  }
}
const result = getTravel(0);
console.log(`我要去${result}`); //我要去北京

中级程序员写法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const Status = {
  BEIJING: 0,
  SHANGHAI: 1,
  HANGZHOU: 2,
};

function getTravel(status: any) {
  if (status === Status.BEIJING) {
    return "北京";
  } else if (status === Status.SHANGHAI) {
    return "上海";
  } else if (status === Status.HANGZHOU) {
    return "杭州";
  }
}

const result = getTravel(Status.BEIJING);

console.log(`我要去${result}`); //我要去北京

高级程序员写法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
enum Status {
  BEIJING,
  SHANGHAI,
  HANGZHOU,
}

function getServe(status: any) {
  if (status === Status.BEIJING) {
    return "北京";
  } else if (status === Status.SHANGHAI) {
    return "上海";
  } else if (status === Status.HANGZHOU) {
    return "杭州";
  }
}

const result = getServe(Status.BEIJING);

console.log(`我要去${result}`); //我要去北京

17.2 枚举类型的对应值

上面的你调用时传一个 0,也会输出 我要去北京

1
2
const result = getServe(0);
console.log(`我要去${result}`); //我要去北京

这是因为枚举类型是有对应的数字值的,默认是从 0 开始的。我们直接用console.log()就可以看出来了:

1
2
3
console.log(Status.BEIJING); // 0
console.log(Status.SHANGHAI); // 1
console.log(Status.HANGZHOU); // 2

那这时候不想默认从 0 开始,而是想从 1 开始。可以这样写:

1
2
3
4
5
enum Status {
  BEIJING = 1,
  SHANGHAI,
  HANGZHOU,
}

17.3 枚举通过下标反查

我们这里能打印出枚举的值(也有叫下标的),那如果我们知道下标后,也可以通过反差的方法,得到枚举的值:

1
console.log(Status.BEIJING, Status[0]);
本文由作者按照 CC BY 4.0 进行授权