引言
本文收录于TypeScript知识总结系列文章,欢迎指正!
在编写JS代码时,我们通常使用const、var、let来定义一个变量,进行变量的运算或者逻辑编写等。在TS中也有一种类似变量的写法那就是类型别名,与运算逻辑相对应的便是类型符号
类型别名
在前面的文章中,我们介绍了TypeScript中常用的类型,然而在实际项目开发中,不可避免的会遇到各种复杂类型,有些可以使用接口或对象类型来描述,但是有些数据类型更灵活、复杂,那么此时,类型别名是一种非常有用的工具,它可以帮助我们简化代码,提高代码的可读性和可维护性;
基本用法
类型别名使用 type 类型名 = 类型值 的方式来定义,如
interface IFn { (): void } interface IDog { name: string age?: number [key: string]: unknown } type myStr = string// 字符串 type myNum = number// 数字 type myArr = string[]// 数组 type myFn = IFn// 函数 type myDog = IDog// 接口 type myObj = { str: string num: number arr: myArr }// 对象 const str: myStr = "string" const num: myNum = 10 const arr: myArr = ['a', 'b'] const fn: myFn = () => null const dog: myDog = { name: "阿黄" } const obj: myObj = { str: "string", num: 11, arr: arr }
字面量类型
字面量类型(Literal Types)用来表示具体的字面量值,包括字符串、数字、布尔值等。它们可以作为类型注解的一部分,用来限制变量、函数参数、函数返回值等的取值范围
数字字面量
数字字面量类型(Numeric Literal Types)是一种用来表示具体数字值的类型,它使用一个具体的数字来定义一个类型
type Num = 10 const num: Num = 10 const num2: Num = 20 // 抛错,不能将20赋值给类型10
字符串字面量
字符串字面量类型(String Literal Types):用来表示一个具体的字符串值的类型。
type Str = "a" const num: Str = "a"
布尔字面量
布尔字面量类型(Boolean Literal Types):用来表示一个具体的布尔值的类型。
type Bool = false const bool: Bool = false
空字面量
空字面量类型(Empty Literal Types):用来表示一个空值的类型,被定义的类型只能被赋值为undefined
或null
type Void = void; const isNull: Void = null const isUndefined: Void = undefined const isVoid: Void = void 0
枚举字面量
枚举字面量类型(Enum Literal Types):用来表示一个具体的枚举值的类型
enum Color { Red = 1, Green, Blue } type colorBlue = Color.Blue const blue: colorBlue = 3
类型符号
如果说类型别名是一个人,那么类型符号就是它的灵魂。在TypeScript中,类型符号是构建类型系统的基础,是实现类型别名的核心。
联合类型
联合类型(Union Types)用来表示一个变量可以包含多种类型之一的情况。联合类型使用或符号 | 来连接两个或多个类型,例如:
let strOrNum: string | number = 10 strOrNum = "a"
我们使用上面讲到的类型别名试试
type strOrNum = string | number const str: strOrNum = "a" const num: strOrNum = 10
交叉类型
交叉类型(Intersection Types)可以用来将多个类型合并为一个类型。交叉类型使用且符号&进行连接两个或多个类型;值得注意的是交叉类型一般使用于对象定义这种复杂的数据类型,如果使用交叉类型定义基础类型,则会转换为never类型,因为一个类型不可能同时兼备两种基础类型,如
type str = string type num = number type StrAndNum = str & num // never类型
上面代码中number和string是互斥的类型,不存在既是number又是string的值,然而程序不会逻辑报错,因为number和string会包装成对象类型
下面是一个交叉类型的案例
type Animal = { name: string age?: number } type Dog = { readonly color: string getColor(): string } type WhiteDog = Animal & Dog const whiteDog: WhiteDog = { name: "阿黄", color: "white", getColor() { return this.color }, }
tips:交叉类型和接口继承有点类似,如果对象同时存在同名不同类型的属性时会抛错
类型断言
类型断言(Type Assertion)是一种显式地告诉编译器变量的类型的方式,它允许我们手动指定一个变量的类型,从而绕过TypeScript编译器对该变量类型的检查。
断言的使用方式有两种,分别是尖括号和as关键字
尖括号
let str: unknown const len: number = (<string>str).length
在上面的代码中,我们使用断言的方式将str转换为字符串类型,并获取字符串的长度
as关键字
let str: unknown const len: number = (str as string).length
与尖括号一样,在变量的后面增加 as 类型 使用类型断言
非空断言
在JS中,我们通常使用可选链操作符( .? )来判断一个对象是否为空,来增加代码健壮性
在TS中,我们可以使用非空断言(Non-null Assertion)来告诉编译器变量或表达式一定不为空
在下面代码中,str可能是null或者字符串类型,若直接使用str.length会提示:对象可能为"null"
const str: string | null = null const len: number = str.length
tips:上述代码没抛错可能因为在tsconfig中未开启strictNullChecks
此时我们可以通过 ! 非空断言或 ? 可选链操作符以及 ?? 空值合并操作符告诉编译器str不为null或处理变量为空的异常
let str: string | null = null if (window) { str = "abc" } const len: number = str!.length const len2: number = str?.length ?? 10
类型保护
typeof
TypeScript的typeof和JavaScript的typeof在语法上是相同的,但是在行为上有区别。在TS中typeof可以获取变量的类型,如
let num = 10 let str = 'abc' type myStr = typeof str type myNum = typeof num const str1: myStr = "bcd" const num1: myNum = 20
instanceof
instanceof与JS中的使用方式相同,animal instanceof Animal 表示animal是不是Animal的实例化对象,这里就不多赘述
class Animal { } const animal = new Animal() console.log(animal instanceof Animal);
类型谓词
类型谓语在函数运行时检查返回值的类型,并且进行类型保护,它的作用是缩小类型范围。类型谓语使用is表示,下面是一个简单的例子
interface IAnimal { name: string age: number } function isAnimal(animal): animal is IAnimal { return "name" in animal } const animal: any = { name: '阿黄', age: 10 }; if (isAnimal(animal)) { console.log(animal.name) }
可以看到,我们定义了一个IAnimal接口,其中有两个属性,在isAnimal函数中我们把函数返回值写成类型谓语的形式,表示返回值的animal就是接口IAnimal的实现,此时在下方取animal.name时是正常的。
此时我们重新定义一个接口IDog
interface IDog { color: string hobby: string }
将上面的isAnimal函数改成这样,此时再执行isAnimal函数后,再去访问name属性就会抛错
function isAnimal(animal): animal is IDog { return "name" in animal } if (isAnimal(animal)) { console.log(animal.name) //报错,类型“IDog”上不存在属性“name” }
除此之外我们同样可以结合交叉类型,联合类型等灵活的使用is
索引类型
在TS中我们可以使用keyof关键字来获取一个对象类型或接口的类型的所有键的联合类型。
interface IDog { name: string color: string age: number } type IDogKeys = keyof IDog // 相当于 'name' | 'color' | 'age' const dogAge: IDogKeys = "age" const dogColor: IDogKeys = "color" const dogName: IDogKeys = "name"
上述代码的IDogKeys相当于获取了IDog的所有键的联合类型,使用类型别名描述对象的方式同样可以达到效果
type IDog = { name: string color: string age: number } type IDogKeys = keyof IDog // 相当于 'name' | 'color' | 'age' const dogAge: IDogKeys = "age" const dogColor: IDogKeys = "color" const dogName: IDogKeys = "name"
映射类型
in运算符在TypeScript和JavaScript中都存在,并且用法基本相同,用来检查某个属性是否存在于某个对象或其原型链中
interface IDog { name: string color?: string } const dog: IDog = { name: "阿黄" } console.log('name' in dog);// true console.log('color' in dog);// false
使用索引类型加映射类型限制索引区间
interface IDog { name: string color?: string } type ISmallDog = { [key in keyof IDog]: string// 这行表示只能取IDog的key } const dog: ISmallDog = { name: "阿黄",//此时的type需要与ISmallDog的相同 hobby: 'ball'// 报错,IDog找不到hobby }
type or interface?
Type和Interface都是用于定义类型的关键字,那么其二者分别适用什么场景?
二者的区别:
- Type可以声明基本类型、联合类型、元组、枚举、函数等,而Interface主要用于定义对象、类、函数等复杂类型
- Type是类型别名的作用,不能被扩展,而Interface可以被继承
- Type可以使用联合类型,交叉类型等高级类型进行操作,而Interface不支持
应用场景
Type:定义一个基本类型、联合类型、元组、枚举等类型,或需要使用高级类型操作的类型
Interface:定义对象、类等复杂类型,或使用对类型扩展,继承等操作时接口更适用
tips:使用type定义的对象类型同样可以被类实现
type IAnimel = { name: string getName(): string } class Animal implements IAnimel { name = 'dog' getName() { return this.name } }
总结
本文从类型别名和类型符号两个模块;介绍了类型别名的使用及字面量类型,并且从联合类型,交叉类型等高级类型操作的方面将类型别名的应用场景连接起来,最后将类型别名对比接口介绍了二者的使用场景