TypeScript的类型系统允许我们使用多种运算符,从现有类型中构建新类型
联合类型
使用 或运算符 来讲多种类型组合 形成的类型 叫做 联合类型(Union Type)
- 联合类型是由两个或者多个其他类型组成的类型
- 表示可以是这些类型中的任何一个值
- 联合类型中的每一个类型被称之为联合成员(union's members)
function printId(id: number | string) {
console.log(id)
// 如果我们需要使用联合类型,需要使用类型缩小将联合类型缩小为某一个具体的类型,从而保证js代码的健壮性
if (typeof id === 'string') {
console.log(id.length)
}
}
交叉类型
联合类型表示多个类型中一个即可,而交叉类似表示需要满足多个类型的条件
交叉类型使用 & 符号进行连接
// 需要一个类型 即满足 是 number 又需要是string
// 不存在这样的类型 所以newType的类型是 never
type newType = number & string
所以实际开发中,主要是在对象类型上使用交叉类型
interface IName {
name: string
}
interface IAge {
age: number
}
const user: IName & IAge = {
name: 'Klaus',
age: 23
}
类型别名
我们通过在类型注解中编写 对象类型 和 联合类型,但是当我们想要多次在其他地方使用时,就要编写多次
所以我们可以给对应的类型起一个别名,以便于多次调用
type Point = {
// 字段和字段之间使用分号或者逗号或者换行进行分割
x: number
y: number
}
接口
我们除了可以通过类型别名定义一个新的类型,也可以通过接口定义一个新的类型
interface Point {
x: number
y: number
}
接口和类型别名的区别
类型别名和接口非常相似,在定义对象类型时,大部分时候,你可以任意选择使用
// 定义类型别名是通过 赋值的方式进行定义的
type Point = {
x: number
y: number
// 可选类型
z?: number
}
// 定义接口 是通过 类型声明的方式进行定义的
interface IPoint {
x: number
y: number
z?: number
}
// 类型别名可以为非对象类型起别名,也可以给对象类型起别名
// 但是接口 只能给对象类型起别名
type NewType = number | string // => 联合类型不是对象类型,所以只能使用类型别名
interface 可以重复定义某个接口中的属性和方法,多个同名接口会合并成一个接口
type的作用是对类型起别名,别名是不能重复的
// error -> Point不能重复定义
type Point {
x: number
y: number
}
type Point {
z: number
}
interface Point {
x: number
y: number
}
interface Point {
z: number
}
const point: Point = {
x: 100,
y: 100,
z: 100
}
接口支持继承,类型别名不可以
// 接口命名 一般使用 I + 大驼峰
interface IPerson {
name: string
age: number
}
interface Student extends IPerson {
sno: string
}
const stu: Student = {
name: 'Klaus',
age: 23,
sno: '1800166'
}
如果是用来定义对象类型,那么推荐使用接口来进行定义
但是如果是用来定义其它数据类型的类型,推荐使用type来进行定义
类型断言
有时候TypeScript无法获取具体的类型信息,这个我们需要使用类型断言(Type Assertions)
TypeScript只允许类型断言转换为 更具体 或者 不太具体 的类型版本,此规则可防止不可能的强制转换
类型断言有两种书写方式:
xxxx as string--- 推荐的写法<string> xxxx--- 不推荐 因为在在JSX中不支持这种写法,会被识别为标签
// 对于document.getElementById 获取到的结果只能是 HTMLElement | undefined
// 所以此时imgEl上不能使用src, alt 这类的img元素特有的属性,此时可以使用类型断言
const imgEl = document.getElementById('avatar') as HTMLImageElement | undefined
if (imgEl) {
imgEl.src = ''
imgEl.alt = ''
}
非空类型断言 !
非空断言使用的是 ! ,表示可以确定某个标识符是有值的,跳过ts在编译阶段对它是否为空的检测
const imgEl = document.getElementById('avatar') as HTMLImageElement | undefined
// 非空类型断言 本质上是不安全的 所以需要谨慎使用
imgEl!.src = ''
imgEl!.alt = ''
字面量类型
单独使用字面量类型是没有太大的意义的,但是我们可以将多个类型联合在一起
type Direction = 'left' | 'right' | 'up' | 'down'
字面量推理
const user = {
name: 'Klaus',
age: 18
}
/*
const user: {
name: string
age: number
}
*/
// as const 可以将 更为宽泛的类型 转换为 更为具体的字面量类型
// 对一个对象类型进行字面量推理的时候,对应的对象类型的属性类型都是只读的
const user = {
name: 'Klaus',
age: 18
} as const
/*
const user: {
readonly name: "Klaus";
readonly age: 18;
}
*/
类型缩小
类型缩小的英文是 Type Narrowing(也有人翻译成类型收窄)
我们可以通过类似于 typeof padding === "number" 的判断语句,来改变TypeScript的执行路径
在给定的执行路径中,我们可以缩小比声明时更小的类型,这个过程称之为 缩小( Narrowing )
而我们编写的 typeof padding === "number 也可以称之为 类型保护(type guards)
常见的类型保护方式
typeof
function printID(id: number | string) {
if (typeof id === 'string') {
console.log(id.toUpperCase())
}
}
平等缩小
可以使用Switch或者相等的一些运算符来表达相等性(比如===, !==, ==, and != )
type Direction = 'left' | 'right'
function checkDirection(direction: Direction) {
if (direction === 'left') {
console.log('left')
} else {
console.log('right')
}
}
instanceof
// 实例对象的类型就是其对应的类
function getDate(date: string | Date) {
if (date instanceof Date) {
console.log(date.toISOString())
}
}
in操作符
Javascript 有一个运算符,用于确定对象是否具有带名称的属性:in运算符
如果指定的属性在指定的对象或其原型链中,则in 运算符返回true
interface ISwim {
swim: () => void
}
interface IRun {
run: () => void
}
const fish: ISwim = {
swim() {}
}
const cat: IRun = {
run() {}
}
function checkAnimal(animal: ISwim | IRun) {
if ('swim' in animal) {
animal.swim()
} else {
animal.run()
}
}