TypeScript

TypeScript

常用的 TS 类型定义

原始数据类型

原始数据类型 JavaScript TypeScript
字符串 String string
数值 Number number
布尔值 Boolean boolean
大整数 BigInt bigint
符号 Symbol symbol
不存在 Null null
未定义 Undefined undefined
// 字符串
const str: string = 'Hello World'

// 数值
const num: number = 1

// 布尔值
const bool: boolean = true

不过在实际的编程过程中,原始数据类型的类型定义是可以省略的,因为 TypeScript 会根据声明变量时赋值的类型,自动帮推导变量类型,也就是可以跟平时写 JavaScript 一样:

// 这样也不会报错,因为 TS 会帮推导它们的类型
const str = 'Hello World'
const num = 1
const bool = true

数组

除了原始数据类型之外, JavaScript 还有引用类型,数组 Array 就是其中的一种。

数组里的数据 类型写法 1 类型写法 2
字符串 string[] Array
数值 number[] Array
布尔值 boolean[] Array
大整数 bigint[] Array
符号 symbol[] Array
不存在 null[] Array
未定义 undefined[] Array

另外一种写法是基于 TS 的泛型 Array<T>

如果的数组一开始就有初始数据(数组长度不为 0 ),那么 TypeScript 也会根据数组里面的项目类型,正确自动帮推导这个数组的类型,这种情况下也可以省略类型定义

// 这种有初始项目的数组, TS 也会帮推导它们的类型
const strs = ['Hello World', 'Hi World']
const nums = [1, 2, 3]
const bools = [true, true, false]

但是!如果一开始是 [] ,那么就必须显式的指定数组类型:

// 这个时候会认为是 any[] 或者 never[] 类型
const nums = []

// 这个时候再 push 一个 number 数据进去,也不会使其成为 number[]
nums.push(1)

而对于复杂的数组,比如数组里面的 item 都是对象,其实格式也是一样,只不过把原始数据类型换成 对象的类型 即可,例如 UserItem[] 表示这是一个关于用户的数组列表。

对象(接口)

如何定义对象的类型

对象的类型定义有两个语法支持: typeinterface

先看看 type 的写法:

ts

type UserItem = {
  // ...
}

再看看 interface 的写法:

ts

interface UserItem {
  // ...
}

可以看到它们表面上的区别是一个有 = 号,一个没有

TIP

对象的类型定义通常采用 Upper Camel Case 大驼峰命名法,也就是每个单词的首字母大写,例如 UserItemGameDetail ,这是为了跟普通变量进行区分(变量通常使用 Lower Camel Case 小驼峰写法,也就是第一个单词的首字母小写,其他首字母大写,例如 userItem )。

// 定义用户对象的类型
interface UserItem {
  name: string
  age: number
}

// 在声明变量的时候将其关联到类型上
const petter: UserItem = {
  name: 'Petter',
  age: 20,
}

注意,上面这样定义的接口类型,表示 nameage 都是必选的属性,不可以缺少,一旦缺少,代码运行起来就会报错!

可选的接口属性

age 后面紧跟了一个 ? 号再接 : 号,这是 TypeScript 对象对于可选属性的一个定义方式

interface UserItem {
  name: string
  // 这个属性变成了可选
  age?: number
}

const petter: UserItem = {
  name: 'Petter',
}

调用自身接口的属性

如果一些属性的结构跟本身一致,也可以直接引用,比如下面例子里的 friendList 属性,用户的好友列表,它就可以继续使用 UserItem 这个接口作为数组的类型:

interface UserItem {
  name: string
  age: number
  enjoyFoods: string[]
  // 这个属性引用了本身的类型
  friendList: UserItem[]
}

const petter: UserItem = {
  name: 'Petter',
  age: 18,
  enjoyFoods: ['rice', 'noodle', 'pizza'],
  friendList: [
    {
      name: 'Marry',
      age: 16,
      enjoyFoods: ['pizza', 'ice cream'],
      friendList: [],
    },
    {
      name: 'Tom',
      age: 20,
      enjoyFoods: ['chicken', 'cake'],
      friendList: [],
    }
  ],
}

接口的继承

接口还可以继承,比如要对用户设置管理员,管理员信息也是一个对象,但要比普通用户多一个权限级别的属性,那么就可以使用继承,它通过 extends 来实现:

interface UserItem {
  name: string
  age: number
  enjoyFoods: string[]
  friendList: UserItem[]
}

// 这里继承了 UserItem 的所有属性类型,并追加了一个权限等级属性
interface Admin extends UserItem {
  permissionLevel: number
}

const admin: Admin = {
  name: 'Petter',
  age: 18,
  enjoyFoods: ['rice', 'noodle', 'pizza'],
  friendList: [
    {
      name: 'Marry',
      age: 16,
      enjoyFoods: ['pizza', 'ice cream'],
      friendList: [],
    },
    {
      name: 'Tom',
      age: 20,
      enjoyFoods: ['chicken', 'cake'],
      friendList: [],
    }
  ],
  permissionLevel: 1,
}

如果觉得这个 Admin 类型不需要记录这么多属性,也可以在继承的过程中舍弃某些属性,通过 Omit 帮助类型来实现,Omit 的类型如下:

type Omit<T, K extends string | number | symbol>

其中 T 代表已有的一个对象类型, K 代表要删除的属性名,如果只有一个属性就直接是一个字符串,如果有多个属性,用 | 来分隔开,下面的例子就是删除了两个不需要的属性:

interface UserItem {
  name: string
  age: number
  enjoyFoods: string[]
  friendList?: UserItem[]
}

// 这里在继承 UserItem 类型的时候,删除了两个多余的属性
interface Admin extends Omit<UserItem, 'enjoyFoods' | 'friendList'> {
  permissionLevel: number
}

// 现在的 admin 就非常精简了
const admin: Admin = {
  name: 'Petter',
  age: 18,
  permissionLevel: 1,
}

在 TypeScript ,通过类得到的变量,它的类型就是这个类:

// 定义一个类
class User {
 // constructor 上的数据需要先这样定好类型
 name: string

 // 入参也要定义类型
 constructor(userName: string) {
   this.name = userName
 }

 getName() {
   console.log(this.name)
 }
}

// 通过 new 这个类得到的变量,它的类型就是这个类
const petter: User = new User('Petter')
petter.getName() // Petter

类也可以提供给接口去继承:

// 这是一个类
class UserBase {
  name: string
  constructor(userName: string) {
    this.name = userName
  }
}

// 这是一个接口,可以继承自类
interface User extends UserBase {
  age: number
}

// 这样这个变量就必须同时存在两个属性
const petter: User = {
  name: 'Petter',
  age: 18,
}

如果类上面本身有方法存在,接口在继承的时候也要相应的实现,当然也可以借助在 对象(接口) 提到的 Omit 帮助类型来去掉这些方法。

class UserBase {
  name: string
  constructor(userName: string) {
    this.name = userName
  }
  // 这是一个方法
  getName() {
    console.log(this.name)
  }
}

// 接口继承类的时候也可以去掉类上面的方法
interface User extends Omit<UserBase, 'getName'> {
  age: number
}

// 最终只保留数据属性,不带有方法
const petter: User = {
  name: 'Petter',
  age: 18,
}

联合类型

当一个变量可能出现多种类型的值的时候,可以使用联合类型来定义它,类型之间用 | 符号分隔。

Vue 的路由在不同的数据结构里也有不同的类型,有时候需要通过路由实例来判断是否符合要求的页面,也需要用到这种联合类型:

// 注意:这不是完整的代码,只是一个使用场景示例
import type { RouteRecordRaw, RouteLocationNormalizedLoaded } from 'vue-router'

function isArticle(
  route: RouteRecordRaw | RouteLocationNormalizedLoaded
): boolean {
  // ...
}


用 Vue 做页面,会涉及到子组件或者 DOM 的操作,当它们还没有渲染出来时,获取到的是 null ,渲染后才能拿到组件或者 DOM 结构,这种场景也可以使用联合类型:

// querySelector 拿不到 DOM 的时候返回 null
const ele: HTMLElement | null = document.querySelector('.main')

函数

函数两个最核心的操作:输入与输出,也就是对应函数的 “入参” 和 “返回值” ,在 TypeScript ,函数本身和 TS 类型有关系的也是在这两个地方。

函数的入参是把类型写在参数后面,返回值是写在圆括号后面:

// 注意:这是 TypeScript 代码

// 写法一:函数声明
function sum1(x: number, y: number): number {
  return x + y
}

// 写法二:函数表达式
const sum2 = function(x: number, y: number): number {
  return x + y
}

// 写法三:箭头函数
const sum3 = (x: number, y: number): number => x + y

// 写法四:对象上的方法
const obj = {
  sum4(x: number, y: number): number {
    return x + y
  }
}


函数的可选参数

// 注意 isDouble 这个入参后面有个 ? 号,表示可选
function sum(x: number, y: number, isDouble?: boolean): number {
  return isDouble ? (x + y) * 2 : x + y
}

// 这样传参都不会报错,因为第三个参数是可选的
sum(1, 2) // 3
sum(1, 2, true) // 6

TIP

需要注意的是,可选参数必须排在必传参数的后面。

无返回值的函数

这种函数用 void 来定义它的返回,也就是空。

// 注意这里的返回值类型
function sayHi(name: string): void {
  console.log(`Hi, ${name}!`)
}

sayHi('Petter') // Hi, Petter!

需要注意的是, voidnullundefined 不可以混用,如果的函数返回值类型是 null ,那么是真的需要 return 一个 null

// 只有返回 null 值才能定义返回类型为 null
function sayHi(name: string): null {
  console.log(`Hi, ${name}!`)
  return null
}

异步函数的返回值

对于异步函数,需要用 Promise<T> 类型来定义它的返回值,这里的 T 是泛型,取决于的函数最终返回一个什么样的值( async / await 也适用这个类型)。

例如这个例子,这是一个异步函数,会 resolve 一个字符串,所以它的返回类型是 Promise<string> (假如没有 resolve 数据,那么就是 Promise<void>

// 注意这里的返回值类型
function queryData(): Promise<string> {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve('Hello World')
    }, 3000)
  })
}

queryData().then((data) => console.log(data))

函数本身的类型

通过函数表达式或者箭头函数声明的函数,这样写好像只对函数体的类型进行了定义,而左边的变量并没有指定。

没错,确实是没有为这个变量指定类型:

// 这里的 sum ,确实是没有指定类型
const sum = (x: number, y: number): number => x + y

这是因为,通常 TypeScript 会根据函数体帮自动推导,所以可以省略这里的定义。

如果确实有必要,可以这样来定义等号左边的类型:

ts

const sum: (x: number, y: number) => number = (x: number, y: number): number =>
  x + y

这里出现了 2 个箭头 => ,注意第一个箭头是 TypeScript 的,第二个箭头是 JavaScript ES6 的。

实际上上面这句代码是分成了三部分:

  1. const sum: (x: number, y: number) => number 是这个函数的名称和类型
  2. = (x: number, y: number) 这里是指明了函数的入参和类型
  3. : number => x + y 这里是函数的返回值和类型

TypeScript 的函数类型是以 () => void 这样的形式来写的:左侧圆括号是函数的入参类型,如果没有参数,就只有一个圆括号,如果有参数,就按照参数的类型写进去;右侧则是函数的返回值。

事实上由于 TypeScript 会帮推导函数类型,所以很少会显式的去写出来,除非在给对象定义方法

// 对象的接口
interface Obj {
  // 上面的方法就需要显式的定义出来
  sum: (x: number, y: number) => number
}

// 声明一个对象
const obj: Obj = {
  sum(x: number, y: number): number {
    return x + y
  }
}

函数的重载

posted @ 2022-10-15 22:21  一星一辰  阅读(78)  评论(0编辑  收藏  举报