最后更新:2026-02-06

说实话,我刚开始接触 TypeScript 的时候是抵触的。觉得 JavaScript 写着挺顺,加了类型约束之后各种报错,到处要加类型标注,很烦。

后来接手了一个老项目,代码里有个函数被七八个地方调用,没有任何注释,参数传的什么鬼全靠猜。改了一个地方,不知道其他地方有没有受影响,提心吊胆。那次之后开始认真对待 TypeScript 了。

最基础的类型注解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 变量类型
let name: string = 'Eric'
let age: number = 28
let isActive: boolean = true

// 数组
let tags: string[] = ['react', 'vue']
let scores: Array<number> = [98, 85, 72]

// 函数参数和返回值
function greet(name: string): string {
return `Hello, ${name}`
}

// 函数没有返回值用 void
function log(msg: string): void {
console.log(msg)
}

这些是最基础的,用起来跟写注释差不多,但机器能读懂。

对象类型和接口

定义对象结构用 interfacetype

1
2
3
4
5
6
7
8
9
10
interface User {
id: number
name: string
email: string
avatar?: string // ? 表示可选字段
}

function getUserName(user: User): string {
return user.name
}

两者很像,有争议。我的习惯是:描述对象结构用 interface,其他情况用 type

1
2
3
4
5
6
// type 可以做联合类型,interface 不行
type Status = 'pending' | 'active' | 'disabled'
type ID = string | number

// type 可以给基本类型起别名
type Username = string

联合类型和类型收窄

联合类型是 TypeScript 里非常常用的特性:

1
2
3
4
5
6
7
type ButtonVariant = 'primary' | 'secondary' | 'danger'

interface ButtonProps {
variant: ButtonVariant
label: string
disabled?: boolean
}

类型收窄(Type Narrowing)是 TypeScript 自动根据判断条件推断类型:

1
2
3
4
5
6
7
8
function format(value: string | number): string {
if (typeof value === 'string') {
// 这里 TS 知道 value 是 string
return value.toUpperCase()
}
// 这里 TS 知道 value 是 number
return value.toFixed(2)
}

泛型:让函数更通用

泛型是 TypeScript 里稍微难一点的概念,但用好了非常有用。

1
2
3
4
5
6
7
8
9
10
11
12
// 没有泛型,只能写死类型
function first(arr: number[]): number {
return arr[0]
}

// 用泛型,适用于任意类型的数组
function first<T>(arr: T[]): T {
return arr[0]
}

first([1, 2, 3]) // T 推断为 number
first(['a', 'b', 'c']) // T 推断为 string

接口里的泛型,在 React 里很常用:

1
2
3
4
5
6
7
8
9
10
11
interface ApiResponse<T> {
code: number
message: string
data: T
}

// 用户列表接口返回
type UserListResponse = ApiResponse<User[]>

// 单个用户接口返回
type UserDetailResponse = ApiResponse<User>

在 React 里怎么用

函数组件的 Props 类型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
interface ButtonProps {
label: string
variant?: 'primary' | 'secondary'
onClick: () => void
children?: React.ReactNode
}

const Button: React.FC<ButtonProps> = ({ label, variant = 'primary', onClick }) => {
return (
<button className={`btn btn-${variant}`} onClick={onClick}>
{label}
</button>
)
}

useState 的类型:

1
2
3
4
5
6
7
8
// 简单类型 TS 可以自动推断,不用写
const [count, setCount] = useState(0)

// 复杂类型需要显式指定
const [user, setUser] = useState<User | null>(null)

// 数组
const [list, setList] = useState<User[]>([])

事件处理函数类型:

1
2
3
4
5
6
7
8
9
// input 的 onChange
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setValue(e.target.value)
}

// button 的 onClick
const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
e.preventDefault()
}

几个常用的工具类型

TypeScript 内置了一堆工具类型,用好了省很多事:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
interface User {
id: number
name: string
email: string
password: string
}

// Partial:所有字段变可选
type UpdateUserDto = Partial<User>
// 等价于 { id?: number; name?: string; email?: string; password?: string }

// Pick:只取某些字段
type UserProfile = Pick<User, 'id' | 'name' | 'email'>

// Omit:去掉某些字段
type PublicUser = Omit<User, 'password'>

// Required:所有字段变必填
type StrictUser = Required<User>

// Record:key-value 对象类型
type RoleMap = Record<string, string[]>
// 等价于 { [key: string]: string[] }

这几个我项目里几乎每天都在用,PartialOmit 尤其多。

类型断言和非空断言

有时候 TS 类型推断比你保守,你比它更清楚某个值的类型:

1
2
3
4
5
6
7
// 类型断言
const canvas = document.getElementById('canvas') as HTMLCanvasElement
const ctx = canvas.getContext('2d')

// 非空断言(! 号),告诉 TS 这个值不会是 null/undefined
const input = document.querySelector<HTMLInputElement>('#search')!
input.focus()

非空断言用的时候要小心,如果你判断错了,运行时还是会报错,只是 TS 不警告你了。

tsconfig 几个重要配置

1
2
3
4
5
6
7
8
9
10
11
12
{
"compilerOptions": {
"strict": true, // 开启严格模式,强烈推荐
"target": "ES2020", // 编译目标
"module": "ESNext",
"jsx": "react-jsx", // React 项目用这个
"baseUrl": "src", // 路径别名基础目录
"paths": {
"@/*": ["./*"] // @ 映射到 src 目录
}
}
}

strict: true 一定要开。虽然一开始会有很多报错要改,但能帮你发现很多潜在问题。

说实话 TypeScript 的学习曲线比我想象的平缓,基础部分很快就能上手。真正复杂的是条件类型、infer、mapped types 这些高级用法,但日常开发用到的机会不多,遇到了再查也来得及。