摘要

上一篇记录了 TypeScript 中的基础类型,比如字面量类型、联合类型、anyunknownvoidnever。这篇继续记录 TypeScript 中更常用的一些类型写法,主要包括对象类型、可选属性、任意属性、函数类型、数组类型、元组类型、枚举类型和类型别名。

本文代码来自 Vue/uni-app 项目中的 .vue 文件,使用的是 Vue 3 的 <script setup lang="ts"> 写法。

一、在 Vue 文件中使用 TypeScript

在 Vue 单文件组件中使用 TypeScript,需要给 script 标签添加 lang="ts"

<script setup lang="ts">
// TypeScript 代码
</script>

如果是在 Vue 3 项目中,还可以配合 script setup 语法,让代码更简洁。

二、object 类型

object 类型表示非原始类型,也就是对象、数组、函数等。

let a: object

它可以接收对象类型的值:

a = {}
a = []
a = function () {}

但是实际开发中,一般不推荐直接使用 object 来描述普通对象。因为 object 只表示“这是一个非原始值”,并不会告诉 TypeScript 这个对象里有哪些属性。

例如:

let user: object = {
  name: 'ts',
  age: 18
}

// user.name // 报错

虽然 user 里面确实有 name,但 TypeScript 只知道它是一个 object,不知道它具体有什么属性。

所以如果要描述对象结构,更推荐使用对象类型。

三、对象类型

对象类型可以明确规定对象中有哪些属性,以及这些属性分别是什么类型。

let b: { name: string; age: number; sex?: string }

这段代码表示:

  • name 是必填属性,类型是 string
  • age 是必填属性,类型是 number
  • sex 是可选属性,类型是 string

正确写法:

b = { name: 'ts', age: 18 }
b = { name: 'ts', age: 18, sex: 'male' }

错误写法:

// b = { name: 'ts' } // 报错,缺少 age 属性

对象类型的好处是可以让数据结构更清楚,也能在写代码时提前发现字段缺失或类型错误。

四、可选属性

可选属性使用 ? 表示。

let user: { name: string; age: number; sex?: string }

这里的 sex?: string 表示 sex 可以有,也可以没有。

user = { name: '张三', age: 18 }
user = { name: '张三', age: 18, sex: 'male' }

可选属性适合用在一些不一定存在的数据上,比如用户头像、备注、描述、标签等。

let article: {
  title: string
  content: string
  description?: string
}

五、任意属性

如果一个对象除了固定属性外,还允许出现其他属性,可以使用索引签名。

let c: { name: string; [propName: string]: any }

这段代码表示:

  • name 属性必须存在,并且类型是 string
  • 其他属性可以有,也可以没有
  • 其他属性名必须是字符串类型
  • 其他属性的值可以是任意类型

示例:

c = { name: 'ts' }
c = { name: 'ts', age: 18 }
c = { name: 'ts', age: 18, isStudy: true }

这里的 [propName: string]: any 就是索引签名。

不过要注意,any 会降低类型安全。如果可以提前确定其他属性的类型,最好不要直接写 any

例如:

let scoreMap: { [key: string]: number }

scoreMap = {
  math: 90,
  english: 95
}

这样表示对象中的每个属性值都必须是 number

六、函数类型

函数类型可以规定参数类型和返回值类型。

let d: (a: number, b: number) => number

这表示 d 是一个函数:

  • 第一个参数是 number
  • 第二个参数是 number
  • 返回值也是 number

赋值时需要满足这个函数类型:

d = function (a: number, b: number): number {
  return a + b
}

也可以写成箭头函数:

d = (a: number, b: number): number => {
  return a + b
}

如果参数类型或返回值类型不符合,就会报错。

// d = function (a: string, b: string): string {
//   return a + b
// }

七、数组类型

TypeScript 中定义数组类型常见有两种写法。

第一种:类型[]

let e: string[]

e = ['ts', 'vue']

这表示数组中的每一项都必须是 string

// e = ['ts', 123] // 报错

第二种:Array<类型>

let f: Array<number>

f = [1, 2, 3]

这表示数组中的每一项都必须是 number

// f = [1, 2, '3'] // 报错

两种写法本质上都可以,日常开发中更常见的是:

let list: string[]

如果类型比较复杂,也可以使用泛型写法:

let users: Array<{ name: string; age: number }>

八、元组类型

元组类型可以理解为“固定长度和固定类型顺序的数组”。

let g: [string, string]

g = ['ts', 'vue']

这里的 g 必须是一个长度为 2 的数组,并且两个元素都必须是字符串。

// g = ['ts'] // 报错,长度不够
// g = ['ts', 18] // 报错,第二个元素必须是 string

再看另一个例子:

let h: [string, number]

h = ['ts', 18]

这里的 h 必须包含两个元素:

  • 第一个元素是 string
  • 第二个元素是 number

元组常用于表示结构固定的数据,例如坐标、表格行、固定格式的返回值等。

let point: [number, number]

point = [120, 30]

九、枚举类型

枚举使用 enum 定义,适合表示一组有名字的常量。

enum Color {
  Red,
  Green,
  Blue
}

默认情况下,枚举成员会从 0 开始自动编号:

Color.Red   // 0
Color.Green // 1
Color.Blue  // 2

可以使用枚举类型来限制变量的取值范围。

let i: Color

i = Color.Red
i = Color.Green
i = Color.Blue

这样 i 的值就只能来自 Color 这个枚举。

枚举也可以和对象类型一起使用:

let obj: { name: string; color: Color } = {
  name: 'ts',
  color: Color.Red
}

这表示 obj 必须包含:

  • name 属性,类型是 string
  • color 属性,类型是 Color

如果写成其他不属于 Color 的值,就会报错。

十、手动指定枚举值

枚举也可以手动指定值。

enum Direction {
  Up = 1,
  Down = 2,
  Left = 3,
  Right = 4
}

也可以使用字符串枚举:

enum RequestStatus {
  Loading = 'loading',
  Success = 'success',
  Error = 'error'
}

字符串枚举在业务代码中更直观,因为打印出来的值更容易理解。

let status: RequestStatus = RequestStatus.Success

十一、类型别名 type

type 可以用来给一个类型起名字,这个名字就叫类型别名。

type Person = 1 | 2 | 3 | 4

上面代码表示 Person 这个类型只能是 1234 中的一个值。

let j: Person

j = 1
// j = 'hello' // 报错

这里的 j 类型是 Person,所以只能赋值为数字字面量 1234

如果赋值字符串,就会报错:

// j = 'hello'

需要注意,type Person = 1 | 2 | 3 | 4 这里只是一个学习示例。实际开发中,类型别名通常会用来描述更有业务意义的类型。

例如请求状态:

type RequestStatus = 'loading' | 'success' | 'error'

let status: RequestStatus

status = 'loading'
status = 'success'
// status = 'done' // 报错

也可以用来描述对象结构:

type User = {
  name: string
  age: number
  sex?: string
}

let user: User = {
  name: 'ts',
  age: 18
}

类型别名的好处是可以让复杂类型变得更清楚,也方便重复使用。

十二、本文完整练习代码

<template>
  <view class="content"></view>
</template>

<script setup lang="ts">
// object 类型:表示非原始类型的值
let a: object

// 对象类型:必须包含 name 和 age,sex 是可选属性
let b: { name: string; age: number; sex?: string }
// b = { name: 'ts' } // 报错,缺少 age 属性
b = { name: 'ts', age: 18 }
b = { name: 'ts', age: 18, sex: 'male' }

// 索引签名:必须有 name 属性,其他属性可以有也可以没有
let c: { name: string; [propName: string]: any }
c = { name: 'ts' }
c = { name: 'ts', age: 18 }

// 函数类型
let d: (a: number, b: number) => number
d = function (a: number, b: number): number {
  return a + b
}

// 数组类型
let e: string[]
e = ['ts', 'vue']

let f: Array<number>
f = [1, 2, 3]

// 元组类型
let g: [string, string]
g = ['ts', 'vue']

let h: [string, number]
h = ['ts', 18]

// 枚举类型
enum Color {
  Red,
  Green,
  Blue
}

let i: Color
i = Color.Red
i = Color.Green
i = Color.Blue

let obj: { name: string; color: Color } = {
  name: 'ts',
  color: Color.Red
}

// 类型别名
type Person = 1 | 2 | 3 | 4

let j: Person
j = 1
// j = 'hello' // 报错,j 的类型是 Person,不能赋值为字符串
</script>

<style></style>

注意:如果想在项目中正常运行,演示“报错”的代码建议用注释保留,例如 // j = 'hello'。如果不注释,TypeScript 会真的提示类型错误。

十三、学习小结

本文主要记录了 TypeScript 中几种常见类型:

  • object:表示非原始类型,但不能描述具体属性
  • 对象类型:可以约束对象必须有哪些属性
  • 可选属性:使用 ? 表示属性可有可无
  • 索引签名:允许对象拥有额外属性
  • 函数类型:限制函数参数和返回值
  • 数组类型:限制数组中元素的类型
  • 元组类型:限制数组的长度和每个位置的类型
  • 枚举类型:表示一组有名字的常量
  • 类型别名:使用 type 给复杂类型起一个名字

学习 TypeScript 时,不要只记语法,更重要的是理解它在帮我们约束什么。类型写得越清楚,后期代码维护起来就越轻松。

更多推荐