Typescript 的最大优点,就是 类型检查 和 强制约束。它可以让我们在编译运行代码之前,就避免一些类型使用不当、或者没有按照规定书写代码而引起的各种错误。
类型检查器(类型注解 Flow)
记住,TypeScript 的类型检查器,一般设置于你要进行检查的事物的冒号后面。冒号后面,则是你要定义的检查器格式。
原始类型
在 ES6 以后,Javascript 的原始类型一共有 6 种。在 Typescript 中,原始类型也是 6 种,它们分别是:
// 字符串类型
const a: string = "hello world";
// 数字类型
const b: number = 123;
// 布尔类型
const c: boolean = true;
// 空类型
const d: null = null;
// undefined 类型
const e: void = undefined;
// symbol 类型
const f: symbol = Symbol();
引用类型
数组类型
在 Typescript 中,定义数组我们有 3 种方式。分别是:
// Array<类型>,尖括号单一类型定义
const arr1: Array<number> = [1, 2, 3];
// 类型[],中括号单一类型定义
const arr2: number[] = [1, 2, 3];
// [类型,类型,类型],中括号多类型定义
const arr3: [string, number, number] = ["1", 2, 3];
对象类型
在 Typescript 中,定义对象的方式有多种,例如:
// object 类型检查器(支持所有“对象”)
const person: object = function(){}
// 大括号 类型检查器(支持特定类型)
const person: {
name: string;
age?: number // ? 代表该字段为可选,在对象中不一定要包含此属性
} = {
name: "steve",
age: 20
};
如果我们不想使用这么繁琐的 对象类型 定义方式,我们还可以使用 Type 来定义一个类型检查器:
// 定义类型检查器 Person
type Person = {
name: string;
age?: number; // ? 代表该字段为可选,在对象中不一定要包含此属性
};
// 在常量名 冒号 后面使用类型检查器 Person
const person:Person= {
name: "steve", // 该字段必须定义
// age: 23, // 该字段可以不定义
};
函数类型检查器
- 定义 回调函数 类型检查器
const say = (callback: (a: string, b: number) => void) => {
callback("1", 2);
};
say((str, number) => {
console.log(str, number);
});
- 定义 普通函数 类型检查器
// 参数
function square(n: number) {
return n + 1;
}
// 返回值
function square(n: number): number {
return n + 1;
}
// 无返回值
function say(): void {
console.log('hello world')
}
// 这里的 rest 可以使用任意名称,目的为函数增添任意参数
const fn = (a: string, b: string, ...rest: number[]): string => {
let str = a + b + rest;
console.log(rest);
console.log(str);
return str;
};
fn("hello", "world", 1, 2, 3, 4, 5);
或类型检查器
const str: "hello" | "world" | "balala" = "balala";
type StrType = number | string | Array<any>;
断言 asserts
const nums = [1, 2, 3, 4, 5];
const res = nums.find((item) => item > 3);
// 写法一:
const num1 = res as number
// 写法二:
const num2 = <number>res // jsx 下不能这么使用,会冲突
断言的作用是啥呢?
interface Cat {
name: string;
run(): void;
}
interface Fish {
name: string;
swim(): void;
}
function isFish(animal: Cat | Fish) {
// 如果不写 animal as Fish,那么我们传 Cat 类型的实例到 isFish,就会报错
if (typeof (animal as Fish).swim === 'function') {
console.log(true);
}
console.log(false);
}
类型断言只能够欺骗 TS 编译器,无法避免运行时的错误,反而滥用类型断言可能会导致运行时错误。例如:
interface Cat {
name: string;
run(): void;
}
interface Fish {
name: string;
swim(): void;
}
function swim(animal: Cat | Fish) {
(animal as Fish).swim();
}
const tom: Cat = {
name: 'Tom',
run() { console.log('run') }
};
swim(tom);
编译时不会报错,但在运行时会报错 Uncaught TypeError: animal.swim is not a function`,所以,不要轻易使用断言。
接口类型检查器(interface)
- 普通 接口类型检查器 定义
interface Person {
name: string;
age: number;
sex?: string; // 可选
readonly summary: string; // 关键字 readonly,可以使该属性成为只读属性
}
const person: Person = { name: "steve", age: 20, summary: "balala" };
function printPerson(person: Person) {
console.log(person.name);
console.log(person.age);
console.log(person.sex);
// 报错:Cannot assign to 'summary' because it is a read-only property.
person.summary = "lalala"; // summary 为只读属性,
}
- 动态 接口类型检查器 定义
interface State {
page: number; // 页码
total: number; // 总数
// 这里的 string,代表这个键可以定义的类型
[key: string]: number; // 每页条数
}
const state:State = {
page: 1,
total: 100,
pageSize: 30
}
类 class
- 声明一个类
// 定义 Person 类
class Person {
name: string; // this.name
// private,无法使用实例直接访问
private age: number;
// protected,只能在本 class 和子类 class 中才能访问
// readonly,在实例只能被读取,无法被赋值
protected readonly gender: boolean;
// 构造器,为 实例属性 进行赋值
constructor(name: string, age: number) {
this.name = name;
this.age = age;
this.gender = true
}
// 定义实例方法
sayHi(msg: string): void {
console.log(`I am ${this.name}, ${msg}`);
}
}
// 定义 Student 类,使用 extends 进行继承
class Student extends Person {
private constructor(name: string, age: number) {
// 继承父类的 实例属性
super(name, age);
console.log(this.gender);
}
// 定义 静态方法,直接使用 类名 进行调用
static create(name: string, age: number) {
return new Student(name, age);
}
}
const tom = new Person("tom", 18);
console.log(tom.name); // √ name 默认为公有属性(也可以添加 public 修饰符),可以被访问
console.log(tom.age); // X 报错,age 为私有属性,除了类中,无法被外界访问
console.log(tom.gender); // X 报错,gender 为保护属性,只能在本类和子类中访问,无法被外界访问
// X 报错,因为 Student 的 constructor 为 private,故不能被创建
const jack = new Student("steve", 25);
const jack = Student.create("jack", 18);
- class 的接口(interface)
定义类的接口,可以确定在类中需要定义哪些属性和方法。
interface EatAndRun {
eat(food: string): void;
run(distance: number): void;
}
// 使用 EatAndRun 实现了 Person 类
class Person implements EatAndRun {
// 该类中定义了 eat 方法
eat(food: string): void {
console.log(`吃东西:${food}`);
}
// 该类中定义了 run 方法
run(distance: number) {
console.log(`直立行走:${distance}`);
}
}
// 使用 EatAndRun 实现了 Animal 类
class Animal implements EatAndRun {
// 该类中定义了 eat 方法
eat(food: string): void {
console.log(`进食:${food}`);
}
// 该类中定义了 run 方法
run(distance: number) {
console.log(`爬行:${distance}`);
return 1;
}
}
- 抽象类
使用 abstract 修饰词修饰的类,被叫做抽象类。使用 abstract 修饰词修饰的方法,叫做抽象方法。
抽象类不能直接使用(无法 new 出实例),一定要被继承,然后使用继承类。
// 定义一个动物抽象类
abstract class Animal {
eat(food: string): void {
console.log(`狗狗吃了一块:${food}`);
}
// 抽象方法在继承的时候一定要被重写
abstract run(distance: number): void;
}
// 我们定义一个 Dog 类,继承自 Animal 抽象类
class Dog extends Animal {
// 抽象方法,一定要被重写
run(distance: number): void {
console.log(`狗狗跑了${distance}公里`)
}
}
const dog = new Dog();
dog.eat('骨头'); // "狗狗吃了一块:骨头"
dog.run(1); // "狗狗跑了1公里"
interface 和 type 的区别
一般来说,interface(接口) 和 type(类型别名) 可以混用,但是也存在一些差异。
1. 对象/函数
两者都可以用来描述对象的形状或函数签名,但语法不同。
interface,一般来说 name 后面直接写 花括号:
// 约束对象
interface Point {
x: number;
y: number;
}
// 约束函数
interface SetPoint {
(x: number, y: number): void;
}
type,一般来说 name 后面都是接 等号:
// 约束对象
type Point = {
x: number;
y: number;
};
// 约束函数
type SetPoint = (x: number, y: number) => void;
2. 其他类型
与 interface 不同,type 也可用于其他类型,例如 原始类型、联合类型和数组类型。
// 原始类型
type Name = string;
// 对象类型
type PartialPointX = { x: number; };
type PartialPointY = { y: number; };
// union类型
type PartialPoint = PartialPointX | PartialPointY;
// 数组类型
type Data = [number, string];
3. 扩展
两者都可以进行扩展,但同样语法不同。此外,请注意 interface 和 type 不是相互排斥的。interface 可以扩展 type,反之亦然。
// 接口扩展接口
interface PartialPointX { x: number; }
interface Point extends PartialPointX { y: number; }
//类型别名扩展类型别名
type PartialPointX = { x: number; };
type Point = PartialPointX & { y: number; };
// 接口扩展类型别名
type PartialPointX = { x: number; };
interface Point extends PartialPointX { y: number; }
// 类型别名扩展接口
interface PartialPointX { x: number; }
type Point = PartialPointX & { y: number; };
4. 声明合并
与类型别名不同,接口可以定义多次,并将被视为单个接口(所有声明的成员都被合并)。
// These two declarations become:
// interface Point { x: number; y: number; }
interface Point { x: number; }
interface Point { y: number; }
const point: Point = { x: 1, y: 2 };
泛型
什么是泛型?
当我们在使用函数之前,不知道函数内部具体需要什么类型的 数据 的时候,可以在 使用的时候 动态设置。泛型,就是这样的一种写法。
// 在使用之前不知道函数内部需要什么类型,可以在使用的时候动态设置
function createArray<T>(length: number, value: T): T[] {
const arr = Array<T>(length).fill(value);
return arr;
}
// 我们在这里进行函数调用,调用的时候就对传参类型进行了设置
const res = createArray<string>(10, 'hello');
console.log(res);
枚举类型
枚举可以实现 数字类型 的 自增长
定义枚举类型:
enum En {
a = 3, // 3
b, // 4
c, // 5
}
// 获取枚举值
En.a // 3
En['a'] // 3
// 根据枚举值,获取枚举键
En[3] // a,值为 3 的 enum 元素 name
在枚举中,除开数字之外的其他任何类型都无法实现 自增长,一定要全部定义,不然 就会报错。
enum En{
a = 'say', // say
b = 'hello', // hello
c = 'world', // world,这里一定要定义
}
En.a // say
En['a'] // say
En['say'] // undefined