TypeScript - 语法细节

154 阅读5分钟

image.png 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只允许类型断言转换为 更具体 或者 不太具体 的类型版本,此规则可防止不可能的强制转换

// 对于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()
  }
}
英雄不问出处 看到就是学到