TypeScript 基础知识

第 1 节 概述

TypeScript 由微软开发,是基于 JavaScript 的一个扩展语言。TypeScript 中增加了静态类型检查、接口、泛型等现代开发特性,更适合大型项目的开发。

TypeScript 无法直接在浏览器中运行,需要先编译为 JavaScript,编译前需要安装环境:

1
2
# 全局安装 typescript
npm install typescript -g

可以对单个文件进行编译:

1
tsc index.ts

也可以设置为自动化编译:

1
2
3
4
# 创建编译控制⽂件 tsconfig.json
tsc --init
# 监视⽬录中的 .ts ⽂件变化
tsc --watch

一般来说,现代的前端框架会自动对 TypeScript 进行编译。我们无需自己编译 TypeScript 文件。

第 2 节 基础类型

最简单的类型,通常作为更复杂类型的组成部分,分为字符串、数值和布尔值三类。

1
2
3
let name: string = "Tom"
let age: number = 18
let isLogin: boolean = true

变量的取值严格限制为某一个确定值时,可以使用字面量类型,让“值本身”成为类型的一部分。

1
let direction: "left" = "left"

第 3 节 拼接类型

拼接类型是由多个类型拼接而成的复杂类型。

3.1 类型推断

TypeScript 会对变量的类型进行推断,即便我们不写变量类型,也会进行静态类型检查。

1
2
// TypeScript 会推断出变量 a 的类型是数值
let a = 10

在我们写拼接类型时,可以依据类型推断,不影响语义的同时,简化变量的申明步骤。

3.2 函数类型

1
2
3
4
5
6
7
// 可以在申明时指定数据类型
let count: (a: number, b: number) => number
count = function (x, y) { return x + y }
// 也可以在创建时指定数据类型
let count = function (x: number, y: number): number {  
    return x + y  
}

3.3 对象类型

type 可以为任意类型创建别名,可以方便地进行类型复用和扩展。适用于定义需要多次使用的类型。

1
2
3
// 必须有 name 属性,age 为可选属性
type Person = { name: string, age?: number }  
let person: Person = {name: '张三'}

3.4 联合类型

表示允许取几种类型中的任意一种,比如“字面量”就是一种特殊的类型:

1
2
3
// 性别这个类型就比较常用,定义为男或女
type Gender = '男' | '女';
let gender: Gender = '男'

3.5 交叉类型

表示要满足全部的要求。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
//⾯积
type Area = {
	height: number; //⾼
	width: number; //宽
};

//地址
type Address = {
	num: number; //楼号
	cell: number; //单元号
	room: string; //房间号
};

// 定义类型 House ,且 House 是 Area 和 Address 组成的交叉类型
type House = Area & Address;
	const house: House = {
	height: 180,
	width: 75,
	num: 6,
	cell: 3,
	room: '702'
};

3.6 枚举类型

枚举类型用来表示有限的、固定的类型。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
enum Direction {
	Up,
	Down,
	Left,
	Right,
}
function walk(n: Direction) {
	if (n === Direction.Up) {
		console.log("向上⾛");
	} else if (n === Direction.Down) {
		console.log("向下⾛");
	} else if (n === Direction.Left) {
		console.log("向左⾛");
	} else if (n === Direction.Right) {
	console.log("向右⾛");
	} else {
		console.log("未知⽅向");
	}
}
walk(Direction.Up)
let x = Direction.Down

3.7 数组类型

数组可以直接定义,也可以通过泛型方式进行定义:

1
2
3
4
5
let arr1: string[]
let arr2: Array<string>
	
arr1 = ['a','b','c']
arr2 = ['hello','world']

3.8 元组类型

当一个变量本质上是数组,但每个位置都有明确含义时,使用元组会比普通数组更清晰。

1
2
3
//// 第⼆个元素可选,如果存在,必须是 boolean 类型。
type Count = [number, boolean?]  
let count: Count = [10, true]

第 4 节 输入/输出

4.1 无返回值

void 表示函数无返回值。

1
2
3
function log(msg: string): void {
  console.log(msg)
}

4.2 不可信输入

在处理外部输入时,不清楚其类型,可以从 unknow 开始,先验证再使用。

1
2
3
4
5
function handle(input: unknown) {
  if (typeof input === "string") {
    input.toUpperCase()
  }
}

4.3 穷尽检查

never 表示当所有可能情况都被处理完之后,仍然不应该存在的分支,一般由 TypeScript 推断出来,可以借助 never 发现程序中无法执行或者不会有任何返回值(比如抛出异常)的分支。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
type Action = "add" | "remove"

function handleAction(a: Action) {
  switch (a) {
    case "add":
      return
    case "remove":
      return
    default:
      const _never: never = a
  }
}

第 5 节 类

5.1 属性修饰符

修饰符 含义 具体规则
public 公开的 可被类内部、子类、类外部访问。
protected 受保护的 可被类内部、子类访问。
private 私有的 可被类内部访问。
readonly 只读属性 属性无法修改。

5.2 类

在 JavaScript 中类的属性需要在构造函数外部申明,在 TypeScript 中可以只在构造函数中申明一次。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
class Person {  
    constructor(  
        // name 为 readonly 属性,无法修改  
        public readonly name: string,  
        // age 为 protected 属性,只能在类内部和子类中使用  
        protected age: number,  
        // IDCard 为 private 属性,只能在类内部使用  
        private IDCard: string  
    ) {  
    }  
  
    private getPrivateInfo() {  
        // 类内部可以访问 private 属性  
        return `身份证号码为: ${this.IDCard}`  
    }  
  
    getInfo() {  
        return `我叫: ${this.name},今年${this.age}岁`;  
    }  
}

5.3 接口

interface 为类、对象、函数等定义格式,不能包含任何实现。interfacetype 的功能十分接近,很多场景下可以互换,区别在于:

  • interface 更专注于定义对象和类的结构,支持继承、合并。
  • type可以定义类型别名、联合类型、交叉类型,但不支持继承和自动合并。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// 使⽤ interface 定义 Person 对象
interface PersonInterface {
	name: string;
	age: number;
	speak(): void;
}
// 使⽤ type 定义 Person 对象
type PersonType = {
	name: string;
	age: number;
	speak(): void;
};

接口可以实现继承、合并:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
interface PersonInterface {
	name: string; // 姓名
	age: number; // 年龄
}

interface PersonInterface {
	speak: () => void;
}

interface StudentInterface extends PersonInterface {
	grade: string; // 年级
}

const student: StudentInterface = {
	name: '李四',
	age: 18,
	grade: '高二',
	speak() { console.log(this.name, this.age, this.grade); }
}

type 也可以通过交叉类型实现:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
// 使⽤ type 定义 Person 类型,并通过交叉类型实现属性的合并
type PersonType = {
	name: string; // 姓名
	age: number; // 年龄
	} & {
	speak: () => void;
};

// 使⽤ type 定义 Student 类型,并通过交叉类型继承 PersonType
type StudentType = PersonType & {
	grade: string; // 年级
};
const student: StudentType = {
	name: '李四',
	age: 18,
	grade: '⾼⼆',
	speak() {
	console.log(this.name, this.age, this.grade);
	}
};

5.4 抽象类

如果某一类对象不仅有结构要求,还有一部分共同行为,同时又希望子类必须补充关键逻辑,就可以使用抽象类。抽象类和接口的区别在于:

  • 接口:只能描述结构,不能有任何实现代码,一个类可以实现多个接口。
  • 抽象类:既可以包含抽象方法,也可以包含具体方法,一个类只能继承一个抽象类。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
abstract class Animal {
  move(): void {
    console.log("moving")
  }

  abstract speak(): void
}

class Dog extends Animal {
  speak() {
    console.log("wang")
  }
}

第 6 节 泛型

泛型允许我们在定义函数、类或接口时,使用类型参数来表示未指定的类型,这些参数在具体使用时,才被指定具体的类型。泛型能让同一段代码适用于多种类型。同时仍然保持类型的安全性。

1
2
3
4
5
function logData<T, U>(data1: T, data2: U): void {  
    console.log(data1, data2)  
}  
  
logData<number, string>(100, 'hello')

可以为泛型设置一定的约束条件:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// 泛型约束
TypeScript
interface LengthInterface {
    length: number
}

// 传入的类型 T 必须具有 length 属性
function logPerson<T extends LengthInterface>(data: T): void {
    console.log(data.length)
}