在学习 TypeScript 的过程中,我们经常会遇到各种各样的符号。
例如:
?:表示可选属性,也可以用于可选链。!:表示非空断言。|:表示联合类型。&:表示交叉类型。=>:表示箭头函数或函数类型。...:表示展开运算符或剩余参数。keyof:获取对象类型的属性名。typeof:获取变量或对象的类型。
这些符号看起来很简单,但它们在 TypeScript 中承担着不同的作用。有些用于定义类型,有些用于控制程序逻辑,还有一些用于实现更复杂的类型操作。
如果只是简单记住符号的含义,很容易在实际开发中混淆。因此,本文将从基础语法、类型系统、函数、泛型和进阶类型几个方面,介绍 TypeScript 中常见符号的作用及使用场景。
一、基础语法中的常见符号
先从最常见的语法符号开始。
1. 冒号 :
冒号主要用于类型标注,告诉 TypeScript 某个变量、参数或属性应该是什么类型。
变量类型声明:
let username: string = 'Tom'
let age: number = 18
let isAdmin: boolean = false函数参数和返回值类型:
function add(a: number, b: number): number {
return a + b
}其中:
a: number:参数a的类型是 number。b: number:参数b的类型是 number。): number:函数返回值类型是 number。
对象属性类型:
interface User {
id: number
name: string
age: number
}使用场景: 变量、函数参数、函数返回值、对象属性等需要明确类型的地方。
2. 等号 =
等号主要用于赋值和默认值设置。
变量赋值:
const username = 'Tom'
let age = 18函数默认参数:
function greet(name: string = 'Guest') {
return `Hello, ${name}`
}
greet() // Hello, Guest
greet('Tom') // Hello, Tom类型别名:
type ID = string | number这里的 = 用于定义类型别名,并不是给变量赋值。
使用场景: 变量赋值、函数默认参数、类型别名定义等。
3. 逗号 ,
逗号用于分隔多个元素,常见于参数、对象和数组等场景。
const user = {
name: 'Tom',
age: 18,
active: true,
}
function add(a: number, b: number) {
return a + b
}4. 分号 ;
分号用于结束语句。
let name: string = 'Tom';
let age: number = 18;TypeScript 和 JavaScript 都支持自动分号插入,因此分号通常不是必需的。是否使用可以根据项目的 ESLint、Prettier 等代码规范统一决定。
5. 点号 .
点号用于访问对象属性或方法。
const user = {
name: 'Tom',
age: 18,
}
console.log(user.name)
console.log(user.age)也可以用于访问模块导出的成员或对象上的方法。
6. 方括号 []
方括号在 TypeScript 中有多种用途。
定义数组类型:
const names: string[] = ['Tom', 'Jerry']
const ages: number[] = [18, 20]定义元组:
const user: [string, number] = ['Tom', 18]元组可以规定数组中不同位置元素的类型。
访问数组元素:
const numbers: number[] = [10, 20, 30]
console.log(numbers[0])访问对象属性:
interface User {
name: string
age: number
}
const user: User = {
name: 'Tom',
age: 18,
}
const key: keyof User = 'name'
console.log(user[key])7. 花括号 {}
花括号主要用于定义代码块、对象和类型结构。
定义对象:
const user = {
name: 'Tom',
age: 18,
}定义接口:
interface User {
name: string
age: number
}定义函数代码块:
function sayHello() {
console.log('Hello TypeScript')
}8. 反引号 `
反引号用于定义模板字符串,可以在字符串中插入变量或表达式。
const name = 'Tom'
const age = 18
const message = `大家好,我叫 ${name},今年 ${age} 岁。`
console.log(message)相比普通字符串,模板字符串在拼接动态内容时更加方便。
二、TypeScript 类型系统中的常见符号
这一部分是 TypeScript 学习的重点,也是日常开发中最容易遇到的内容。
1. 联合类型 |
| 表示联合类型,代表一个值可以是多个类型中的任意一种。
let id: string | number
id = 1001
id = '1001'上面的 id 可以是字符串,也可以是数字。
实际应用:
例如,一个用户 ID 可能来自数据库数字主键,也可能是字符串形式的 UUID:
type UserId = string | number
function getUser(id: UserId) {
console.log(`正在查询用户:${id}`)
}
getUser(1001)
getUser('user-1001')注意: 联合类型并不代表可以随意调用所有类型的方法。如果需要使用某一种类型特有的方法,通常需要先进行类型判断。
function printValue(value: string | number) {
if (typeof value === 'string') {
console.log(value.toUpperCase())
} else {
console.log(value.toFixed(2))
}
}2. 交叉类型 &
& 表示交叉类型,可以将多个类型合并为一个类型。
interface User {
name: string
}
interface Admin {
permissions: string[]
}
type AdminUser = User & Admin使用时,必须同时满足两个类型的要求:
const admin: AdminUser = {
name: 'Tom',
permissions: ['read', 'write'],
}实际应用:
当一个对象需要同时具备多个模块定义的属性时,可以使用交叉类型组合。
例如:
interface BaseInfo {
id: number
name: string
}
interface TimestampInfo {
createdAt: Date
updatedAt: Date
}
type User = BaseInfo & TimestampInfo这样就可以复用不同模块定义的类型。
3. 可选属性 ?
? 用于表示属性或参数是可选的。
interface User {
name: string
age?: number
}age 属性可以存在,也可以不存在。
const user1: User = {
name: 'Tom',
}
const user2: User = {
name: 'Jerry',
age: 20,
}可选函数参数:
function greet(name: string, age?: number) {
console.log(name, age)
}
greet('Tom')
greet('Jerry', 20)注意:可选参数通常需要放在必选参数之后。
4. 非空断言 !
! 在 TypeScript 中常用于非空断言,告诉编译器:这个值不会是 null 或 undefined。
const element = document.getElementById('app')!
element.innerHTML = 'Hello TypeScript'这里的 ! 表示开发者确信 element 一定存在。
使用场景:
当开发者能够确定某个值一定存在,但 TypeScript 无法推断时,可以使用非空断言。
不过需要注意:
! 只会影响 TypeScript 的类型检查,不会在运行时自动检查或创建对象。如果元素实际不存在,代码仍然可能报错。
更安全的方式是进行判断:
const element = document.getElementById('app')
if (element) {
element.innerHTML = 'Hello TypeScript'
}5. 可选链 ?.
可选链用于安全访问可能不存在的对象属性或方法。
interface User {
profile?: {
address?: {
city?: string
}
}
}
const user: User = {}
const city = user.profile?.address?.city
console.log(city)如果访问链条中某个值是 null 或 undefined,表达式就会提前返回 undefined,不会继续访问后面的属性。
实际应用:
在处理接口返回的数据时,经常会遇到某些字段可能为空的情况:
interface ApiResponse {
data?: {
user?: {
name?: string
}
}
}
function getUserName(response: ApiResponse) {
return response.data?.user?.name
}这样可以避免因为访问不存在的属性而出现运行时异常。
6. 空值合并运算符 ??
?? 用于判断左侧的值是否为 null 或 undefined。
如果左侧是 null 或 undefined,就使用右侧的默认值。
const username = undefined ?? 'Guest'
console.log(username) // Guest它与逻辑或运算符 || 有一个重要区别:
const count1 = 0 || 10
const count2 = 0 ?? 10
console.log(count1) // 10
console.log(count2) // 0因为:
||会在左侧是假值时使用右侧值,例如0、''、false。??只有在左侧为null或undefined时才使用右侧值。
使用场景: 接口字段默认值、配置项默认值、可能为空的数据处理。
7. 类型断言 as
as 用于告诉 TypeScript,开发者希望将某个值按照指定类型进行处理。
const value = 'Hello TypeScript' as string在 DOM 操作中也很常见:
const input = document.getElementById('username') as HTMLInputElement
console.log(input.value)注意:
类型断言不会真正改变 JavaScript 运行时的数据类型,也不会自动进行数据转换。
例如:
const value = '123' as unknown as number这里的 value 在运行时仍然是字符串,并不会变成数字。
如果需要真正转换数据,应该使用 Number()、String() 等方法。
三、函数中的常见符号
1. 箭头符号 =>
=> 有两种常见用途:定义箭头函数和描述函数类型。
定义箭头函数:
const add = (a: number, b: number): number => {
return a + b
}也可以简写:
const multiply = (a: number, b: number) => a * b定义函数类型:
type AddFunction = (a: number, b: number) => number
const add: AddFunction = (a, b) => {
return a + b
}这里的 => number 表示函数的返回值类型是 number。
2. 剩余参数 ...
... 可以用于收集多个参数,也可以用于展开数组或对象。
剩余参数:
function sum(...numbers: number[]): number {
return numbers.reduce((total, current) => total + current, 0)
}
console.log(sum(1, 2, 3, 4))数组展开:
const arr1 = [1, 2, 3]
const arr2 = [...arr1, 4, 5]
console.log(arr2)对象展开:
const user = {
name: 'Tom',
age: 18,
}
const newUser = {
...user,
age: 20,
}注意:对象和数组的展开通常是浅拷贝,并不会自动复制所有嵌套对象。
3. void
void 用于表示函数没有有意义的返回值。
function logMessage(message: string): void {
console.log(message)
}它经常用于日志记录、事件处理等不需要返回结果的函数。
四、泛型中的常见符号和关键字
泛型是 TypeScript 非常重要的功能,可以让代码在保持类型约束的同时具备更好的复用性。
1. 泛型 <T>
<T> 通常用于声明一个泛型类型参数。
function identity<T>(value: T): T {
return value
}
const result1 = identity<string>('Hello')
const result2 = identity<number>(100)这里的 T 可以理解为一个类型占位符。
调用函数时,TypeScript 可以根据传入参数自动推断类型:
const name = identity('Tom')
const age = identity(18)2. extends
extends 在 TypeScript 中有多种用途,常见的包括接口继承、泛型约束和条件类型。
接口继承:
interface Animal {
name: string
}
interface Dog extends Animal {
breed: string
}泛型约束:
interface HasLength {
length: number
}
function printLength<T extends HasLength>(value: T) {
console.log(value.length)
}
printLength('Hello')
printLength([1, 2, 3])这里限制了泛型 T 必须具备 length 属性。
3. keyof
keyof 用于获取某个类型的所有属性名,并将它们组成联合类型。
interface User {
name: string
age: number
email: string
}
type UserKeys = keyof User此时 UserKeys 等价于:
type UserKeys = 'name' | 'age' | 'email'实际应用:
可以使用 keyof 限制函数参数,只允许传入对象中真实存在的属性名。
function getProperty<T, K extends keyof T>(
obj: T,
key: K
): T[K] {
return obj[key]
}
const user = {
name: 'Tom',
age: 18,
}
const name = getProperty(user, 'name')
const age = getProperty(user, 'age')如果传入不存在的属性名,TypeScript 就会给出类型错误。
4. typeof
typeof 可以用于获取变量的类型。
const user = {
name: 'Tom',
age: 18,
}
type User = typeof user此时 User 会根据 user 变量的结构推导出对应类型。
还可以结合 keyof 使用:
type UserKeys = keyof typeof user注意:TypeScript 中的 typeof 类型查询与 JavaScript 运行时的 typeof 运算符有所区别。
JavaScript 运行时:
console.log(typeof 'Hello') // stringTypeScript 类型查询:
const config = {
port: 3000,
}
type Config = typeof config5. in
in 在 TypeScript 中常见于映射类型,也可以用于 JavaScript 运行时的属性判断。
映射类型:
type User = {
name: string
age: number
}
type ReadonlyUser = {
readonly [K in keyof User]: User[K]
}这里的 in 会遍历 User 的所有属性,并生成新的类型。
运行时属性判断:
const user = {
name: 'Tom',
age: 18,
}
console.log('name' in user) // true两种用法需要区分:前者属于类型系统,后者属于 JavaScript 表达式。
五、接口和类型定义中的常见关键字
1. interface
interface 用于定义对象的结构和类型约束。
interface User {
id: number
name: string
age?: number
}
const user: User = {
id: 1,
name: 'Tom',
}接口可以通过 extends 继承其他接口:
interface Admin extends User {
permissions: string[]
}2. type
type 用于定义类型别名,可以表示对象类型、联合类型、交叉类型、函数类型等。
type ID = string | number
type User = {
id: ID
name: string
}
type Status = 'success' | 'error' | 'loading'interface 和 type 都可以描述对象结构,但它们并不完全相同。
简单来说:
interface更适合定义对象结构,并支持接口继承和声明合并。type更灵活,可以定义联合类型、交叉类型、条件类型等。
3. readonly
readonly 用于限制属性只能读取,不能在初始化后重新赋值。
interface User {
readonly id: number
name: string
}
const user: User = {
id: 1,
name: 'Tom',
}
user.name = 'Jerry'
// user.id = 2 // 类型错误需要注意,readonly 是 TypeScript 的类型检查约束,不会自动冻结运行时对象。如果需要运行时不可修改,可以考虑 Object.freeze()。
4. implements
implements 用于检查类是否满足某个接口定义。
interface Animal {
name: string
eat(): void
}
class Dog implements Animal {
name = '旺财'
eat() {
console.log('Dog is eating')
}
}如果类没有实现接口要求的属性或方法,TypeScript 就会报错。
5. enum
enum 用于定义一组命名的常量。
enum Status {
Pending = 0,
Success = 1,
Failed = 2,
}
const currentStatus: Status = Status.Success在实际项目中,也可以使用字符串字面量联合类型:
type Status = 'pending' | 'success' | 'failed'两种方式各有适用场景。简单状态标识通常可以优先考虑联合类型;需要枚举成员或相关枚举行为时,可以考虑 enum。
六、其他常见运算符
除了前面介绍的符号,还有一些 JavaScript 和 TypeScript 中经常使用的运算符。
注意:同一个符号在不同语法上下文中可能有不同含义。例如,! 既可以表示逻辑非,也可以表示 TypeScript 非空断言;+ 既可以做数值加法,也可以做字符串拼接。
七、TypeScript 进阶类型中的常见符号
掌握基础语法之后,可以进一步了解一些更强大的类型操作。
1. 条件类型 T extends U ? X : Y
条件类型可以根据类型之间的关系选择不同的类型。
type IsString<T> = T extends string ? true : false
type A = IsString<string> // true
type B = IsString<number> // false可以理解为类型层面的条件判断:
如果 T 可以赋值给 U,就使用 X;否则使用 Y。
2. 索引访问类型 T[K]
索引访问类型可以根据属性名获取对象中对应属性的类型。
interface User {
name: string
age: number
}
type UserName = User['name'] // string
type UserAge = User['age'] // number还可以结合 keyof:
type UserValue = User[keyof User]此时 UserValue 等价于:
type UserValue = string | number3. as const
as const 可以让 TypeScript 尽可能保留字面量类型,并将对象属性或数组元素推断为只读类型。
const config = {
mode: 'production',
port: 3000,
} as const此时:
mode的类型是'production',而不是普通的string。port的类型是3000,而不是普通的number。对象属性被推断为只读。
实际应用:
const STATUS = {
SUCCESS: 'success',
ERROR: 'error',
LOADING: 'loading',
} as const
type Status = typeof STATUS[keyof typeof STATUS]得到的 Status 类型为:
type Status = 'success' | 'error' | 'loading'4. satisfies
satisfies 用于检查一个表达式是否满足指定类型,同时尽可能保留表达式本身的类型推断。
type Config = {
mode: 'development' | 'production'
port: number
}
const config = {
mode: 'production',
port: 3000,
} satisfies Config它和类型断言 as 不完全一样:
as是告诉 TypeScript 按照指定类型处理。satisfies是检查表达式是否符合指定类型,同时保留更具体的类型信息。
5. 装饰器 @
装饰器是一种特殊语法,可以用于类及其成员的声明和元数据相关操作。
function Log(
target: Function,
context: ClassMethodDecoratorContext
) {
console.log('方法装饰器:', context.name)
}
class UserService {
@Log
getUser() {
return 'Tom'
}
}需要注意,TypeScript 装饰器存在不同的使用模型和配置要求。上面的示例采用较新的标准装饰器形式,实际项目需要确认 TypeScript 版本及所使用框架的支持情况。
八、在 Vue 3 + TypeScript 项目中的实际应用
如果你正在使用 Vue 3 + TypeScript 开发项目,上面这些符号会经常出现在组件、接口和 API 类型定义中。
下面以一个服务监控平台为例。
1. 定义服务类型
export interface Service {
id: number
name: string
url: string
status: 'online' | 'offline' | 'unknown'
responseTime?: number
lastCheckedAt?: string
}这里用到了:
interface:定义服务对象结构。?:表示响应时间和最后检查时间可以不存在。|:定义状态联合类型。
2. 定义 API 响应类型
export interface ApiResponse<T> {
code: number
message: string
data: T
}这里使用了泛型 <T>,让同一个 API 响应结构可以适用于不同的数据类型。
例如:
type ServiceListResponse = ApiResponse<Service[]>3. 定义服务状态颜色
const statusColor = {
online: 'green',
offline: 'red',
unknown: 'gray',
} as const
type ServiceStatus = keyof typeof statusColor这里用到了:
as const:保留具体字面量类型。typeof:获取变量的类型。keyof:获取对象的属性名联合类型。
4. 在 Vue 组件中使用
<script setup lang="ts">
import { ref, computed } from 'vue'
interface Service {
id: number
name: string
status: 'online' | 'offline' | 'unknown'
responseTime?: number
}
const services = ref<Service[]>([])
const onlineCount = computed(() => {
return services.value.filter(
service => service.status === 'online'
).length
})
function updateServiceStatus(
id: number,
status: Service['status']
): void {
const service = services.value.find(item => item.id === id)
if (service) {
service.status = status
}
}
</script>
<template>
<div>
<p>在线服务数量:{{ onlineCount }}</p>
<div v-for="service in services" :key="service.id">
<span>{{ service.name }}</span>
<span>{{ service.status }}</span>
</div>
</div>
</template>这个例子综合使用了:
ref<Service[]>:定义响应式数组类型。computed:计算属性。=>:箭头函数。Service['status']:索引访问类型。?:可选属性。: void:函数返回值类型。
这些都是 Vue 3 + TypeScript 项目中非常常见的写法。
九、学习 TypeScript 符号的建议
TypeScript 的符号和关键字比较多,不建议一开始就试图全部记住。
可以按照下面的顺序学习:
学习时,建议不要只记忆符号的中文含义,而是结合实际代码理解它解决了什么问题。
例如:
什么时候使用
??当一个属性不是必需的。什么时候使用
|?当一个值可能属于多种类型。什么时候使用
&?当一个对象需要同时满足多个类型。什么时候使用
keyof?当需要获取对象的属性名类型。什么时候使用
typeof?当希望从已有变量推导出类型。
十、动手练习
为了更好地理解这些符号,可以尝试完成下面几个练习。
练习一:定义用户类型
定义一个 User 类型,包含以下属性:
id:数字或字符串。name:字符串。age:可选数字。email:可选字符串。
练习二:编写泛型函数
编写一个泛型函数 getFirst,接收一个数组并返回数组的第一个元素。
要求正确推断返回值类型,并处理空数组的情况。
练习三:实现服务状态管理
定义一个服务监控类型,包含服务名称、服务地址和服务状态。
服务状态只能是:
onlineofflineunknown
再编写一个函数,根据服务 ID 修改对应服务的状态。
练习四:组合类型操作
定义一个用户基础类型和一个时间信息类型,通过交叉类型组合成完整用户类型。
然后使用 keyof 获取属性名,编写一个通用的属性读取函数。
总结
TypeScript 中的符号虽然很多,但并不需要一次性全部记住。
对于日常开发来说,最重要的是先掌握基础类型定义、联合类型、交叉类型、可选属性、函数类型和泛型。
随着项目经验增加,再逐步学习 keyof、typeof、条件类型、映射类型等进阶内容。
学习 TypeScript 最好的方式,不是背诵所有符号,而是在实际项目中不断使用它们,理解每种语法背后的设计目的。