TypeScript 深度指南
前言
你已经会写 JavaScript 了,但可能遇到过这些问题:
- 变量赋值了错误类型,运行时才发现
- 对象属性写错了名字,调试半天
- 函数参数类型不对,改来改去
TypeScript 就是在代码运行前帮你发现这些问题的工具。读完这篇,你就能理解 TypeScript 为什么能提升代码质量,看懂类型注解、接口、泛型等核心概念,在 vibecoding 中更好地利用 AI 生成的代码。
这篇文章会带你学什么?
| 章节 | 内容 | 学完能干嘛 |
|---|---|---|
| 第 1 章 | TypeScript 是什么 | 明白它和 JavaScript 的关系 |
| 第 2 章 | 基础类型注解 | 知道怎么给变量标注类型 |
| 第 3 章 | 对象类型与接口 | 定义数据结构的类型 |
| 第 4 章 | 函数类型 | 给函数参数和返回值标注类型 |
| 第 5 章 | 泛型 | 编写可复用的类型安全代码 |
| 第 6 章 | 类型推断与实用技巧 | 知道何时需要显式注解 |
1. TypeScript 是什么
🤔 核心问题
JavaScript 已经够用了,为什么还需要 TypeScript? 多学一门语法值得吗?
1.1 从"运行时出错"到"编译时发现"
🔴 JavaScript 的痛点
- 运行时才发现类型错误
- 拼写错误难以察觉
- 重构时容易遗漏
- IDE 提示不够准确
就像没有拼写检查的文档编辑器
✅ TypeScript 的优势
- 写代码时就发现错误
- 智能提示更准确
- 重构更安全
- 代码更易维护
就像有拼写检查和语法高亮的编辑器
用一句话理解两者的关系:
| 技术 | 比喻 | 作用 |
|---|---|---|
| JavaScript | 原始材料 | 可以直接运行的代码 |
| TypeScript | 蓝图 + 质检 | 给 JavaScript 加类型检查,最后编译成 JavaScript |
1.2 为什么 vibecoding 也需要 TypeScript?
AI 写代码也会出错
一位开发者用 AI 生成了一个用户管理功能。AI 写的 JavaScript 代码能运行,但有个问题:用户年龄应该是数字,但有时候会被错误地赋值为字符串。
结果在计算"是否成年"时,字符串 "25" 被当成字符串处理,导致判断失败。这个 bug 隐藏了很久,直到某个用户输入了非数字字符才暴露出来。
如果用 TypeScript,这段代码在写的时候就会报错:不能将类型 "string" 分配给类型 "number"。
这就是 TypeScript 的价值——在 AI 写错类型时,你能第一时间发现。
1.3 TypeScript 实际上是这样的
TypeScript 不是一门全新的语言,它只是 JavaScript 的"超集":
// 这是有效的 JavaScript,也是有效的 TypeScript
const name = "张三"
const age = 25
function greet(user) {
return `Hello ${user}`
}
// 这是 TypeScript 特有的类型注解
const name2: string = "李四"
const age2: number = 30
function greet2(user: string): string {
return `Hello ${user}`
}关键理解:
- 所有 JavaScript 代码都是有效的 TypeScript 代码
- TypeScript 添加了可选的类型注解
- TypeScript 最终会编译成 JavaScript 运行
💡 核心启示
TypeScript 不会改变代码的运行方式,它只是在编译时帮你检查类型是否正确。你可以渐进地采用 TypeScript——从给关键变量添加类型开始。
2. 基础类型注解
🤔 核心问题
怎么告诉 TypeScript 一个变量应该是什么类型? 类型注解的语法是怎样的?
2.1 类型注解语法
类型注解就是在变量名后面加上: 类型:
// 语法:变量名: 类型 = 值
const name: string = "张三"
let age: number = 25
let isStudent: boolean = true👇 动手试试看:给变量添加类型注解
📝 TypeScript 类型注解演示
const name: string = "张三"const age: number = 25const isActive: boolean = truelet name = "张三"
name = 123 // ✅ 运行时才会报错(可能很晚才发现)let name: string = "张三"
name = 123 // ❌ 编译时立即报错(写代码时就发现)🔍 为什么有些地方不需要类型注解?
TypeScript 可以根据赋值自动推断类型:
// 这些不需要类型注解,TypeScript 能自动推断
const name = "张三" // 推断为 string
const age = 25 // 推断为 number
const isActive = true // 推断为 boolean
// 这些情况需要显式注解
let data // ❌ 错误:不能推断类型
let data: any // ✅ 可以,但失去了类型检查的好处
function add(a, b) { // ❌ 参数类型不明确
return a + b
}
function add2(a: number, b: number): number { // ✅ 类型明确
return a + b
}2.2 基本类型
TypeScript 支持所有 JavaScript 的基本类型:
| 类型 | 说明 | 示例 |
|---|---|---|
string | 字符串 | "hello", '你好' |
number | 数字(整数和小数) | 42, 3.14 |
boolean | 布尔值 | true, false |
null / undefined | 空值 | null, undefined |
array | 数组 | number[], string[] |
object | 对象 | { name: string; age: number } |
数组类型的两种写法:
// 写法 1:类型[](更常用)
const numbers: number[] = [1, 2, 3, 4, 5]
const names: string[] = ["张三", "李四", "王五"]
// 写法 2:Array<类型>
const numbers2: Array<number> = [1, 2, 3, 4, 5]
const names2: Array<string> = ["张三", "李四", "王五"]特殊类型:
// any:任意类型(慎用,相当于关闭类型检查)
let data: any = 42
data = "现在可以是字符串"
data = { name: "张三" } // 也可以是对象
// unknown:类型安全的 any
let value: unknown = 42
// if (typeof value === "number") {
// console.log(value + 10) // 需要先检查类型才能用
// }
// void:没有返回值
function log(message: string): void {
console.log(message)
}
// never:永远不会返回
function error(message: string): never {
throw new Error(message)
}💡 识别技巧
- 看到
: string→ 这是 string 类型的注解 - 看到
: number[]→ 这是数字数组的注解 - 看到
: void→ 这个函数没有返回值
3. 对象类型与接口
🤔 核心问题
怎么定义一个对象的类型? 对象的属性应该是什么类型?
3.1 接口(Interface):定义对象的"形状"
接口是 TypeScript 中定义对象类型的主要方式:
// 定义一个 User 接口
interface User {
id: number
name: string
email: string
age?: number // 可选属性
}
// 使用接口
const user: User = {
id: 1,
name: "张三",
email: "zhangsan@example.com",
age: 25
}
// age 是可选的,可以不提供
const user2: User = {
id: 2,
name: "李四",
email: "lisi@example.com"
}👇 动手试试看:创建符合接口定义的对象
🎯 Interface 接口演示
interface User {
id: number
name: string
email: string
age: number
}const user: User = {
id: 1,
name: "张三",
email: "zhangsan@example.com",
age: 25
} // ✅ 类型完全匹配const user: User = {
id: 1,
name: "张三",
email: "zhangsan@example.com",
age: "25" // ❌ 错误:age 应该是 number,不是 string
}🔍 接口的其他特性
// 只读属性
interface User {
readonly id: number // id 创建后不能修改
name: string
}
const user: User = {
id: 1,
name: "张三"
}
user.id = 2 // ❌ 错误:不能修改只读属性
user.name = "李四" // ✅ 可以修改
// 函数类型
interface User {
name: string
greet: () => string // greet 是一个函数,返回 string
}
const user: User = {
name: "张三",
greet: () => "Hello"
}
// 继承接口
interface Admin extends User {
permissions: string[]
}
const admin: Admin = {
name: "管理员",
greet: () => "Hello Admin",
permissions: ["read", "write", "delete"]
}3.2 类型别名(Type Alias)
除了接口,还可以用 type 定义类型别名:
// 类型别名
type User = {
id: number
name: string
email: string
}
// 联合类型
type Status = "pending" | "success" | "error"
const status: Status = "success" // ✅
// const status2: Status = "failed" // ❌ 错误:不在联合类型中
// 交叉类型(合并多个类型)
type User = {
id: number
name: string
}
type Timestamp = {
createdAt: Date
updatedAt: Date
}
type UserWithTimestamp = User & Timestamp
const user: UserWithTimestamp = {
id: 1,
name: "张三",
createdAt: new Date(),
updatedAt: new Date()
}接口 vs 类型别名:
| 特性 | interface | type |
|---|---|---|
| 扩展 | extends | & 交叉类型 |
| 重复声明 | 会自动合并 | 会报错 |
| 适用场景 | 对象形状、类 | 联合类型、交叉类型、基本类型别名 |
💡 识别技巧
- 看到
interface→ 这是定义对象类型 - 看到
type→ 这是创建类型别名 - 看到
?→ 这是可选属性 - 看到
readonly→ 这是只读属性
4. 函数类型
🤔 核心问题
怎么给函数的参数和返回值标注类型?
4.1 参数类型与返回值类型
// 完整的函数类型注解
function add(a: number, b: number): number {
return a + b
}
// 箭头函数
const multiply = (a: number, b: number): number => {
return a * b
}
// 没有返回值
function log(message: string): void {
console.log(message)
}
// 返回多种类型(联合类型)
function parseInput(input: string): number | string {
const num = parseFloat(input)
return isNaN(num) ? input : num
}4.2 可选参数与默认参数
// 可选参数(用 ? 标记)
function greet(name: string, title?: string): string {
return title ? `${title} ${name}` : name
}
greet("张三") // "张三"
greet("张三", "先生") // "先生 张三"
// 默认参数
function greet2(name: string, title: string = "朋友"): string {
return `${title} ${name}`
}
greet2("李四") // "朋友 李四"
greet2("李四", "博士") // "博士 李四"4.3 函数类型作为参数
// 接受函数作为参数
function calculate(
a: number,
b: number,
operation: (x: number, y: number) => number
): number {
return operation(a, b)
}
calculate(10, 5, (x, y) => x + y) // 15
calculate(10, 5, (x, y) => x * y) // 50
// 更清晰的写法:先定义函数类型
type Operation = (x: number, y: number) => number
function calculate2(
a: number,
b: number,
operation: Operation
): number {
return operation(a, b)
}💡 识别技巧
- 看到
(a: number, b: number) => number→ 这是函数类型,描述参数和返回值 - 看到
: void→ 函数没有返回值 - 看到
?→ 参数是可选的
5. 泛型
🤔 核心问题
怎么编写能处理多种类型、但保持类型安全的代码?
5.1 泛型的基本概念
泛型让你在定义函数、接口或类时,不预先指定具体的类型,而是在使用时再指定:
// 泛型函数:T 是类型变量
function identity<T>(arg: T): T {
return arg
}
// 使用时明确指定类型
const num1 = identity<number>(42) // 类型是 number
const str1 = identity<string>("hello") // 类型是 string
// 类型推断:TypeScript 能自动推断
const num2 = identity(42) // 推断为 number
const str2 = identity("hello") // 推断为 string👇 动手试试看:使用泛型处理不同类型的数据
🔄 泛型 (Generics) 演示
// T 是类型变量,使用时才会确定具体类型
function identity<T>(arg: T): T {
return arg
}
// 泛型数组反转
function reverseArray<T>(arr: T[]): T[] {
return [...arr].reverse()
}📝 泛型使用示例
const nums = [1, 2, 3, 4, 5]
const reversed = reverseArray<number>(nums)
// 结果: [5, 4, 3, 2, 1]
// 类型: number[]const strs = ["a", "b", "c"]
const reversed = reverseArray<string>(strs)
// 结果: ["c", "b", "a"]
// 类型: string[]5.2 泛型约束
限制泛型必须满足某些条件:
// 约束 T 必须有 length 属性
interface HasLength {
length: number
}
function logLength<T extends HasLength>(arg: T): void {
console.log(arg.length)
}
logLength("hello") // ✅ 字符串有 length
logLength([1, 2, 3]) // ✅ 数组有 length
// logLength(42) // ❌ 数字没有 length 属性5.3 泛型接口和类
// 泛型接口
interface Box<T> {
value: T
getValue(): T
}
const numberBox: Box<number> = {
value: 42,
getValue: () => 42
}
const stringBox: Box<string> = {
value: "hello",
getValue: () => "hello"
}
// 泛型类
class Storage<T> {
private items: T[] = []
add(item: T): void {
this.items.push(item)
}
get(index: number): T {
return this.items[index]
}
}
const numberStorage = new Storage<number>()
numberStorage.add(1)
numberStorage.add(2)
// numberStorage.add("string") // ❌ 错误
const stringStorage = new Storage<string>()
stringStorage.add("hello")
// stringStorage.add(1) // ❌ 错误💡 识别技巧
- 看到
<T>→ 这是泛型类型变量 - 看到
<T extends SomeType>→ 泛型约束 - 看到
Array<T>或Promise<T>→ 内置泛型类型
6. 类型推断与实用技巧
🤔 核心问题
什么时候需要显式类型注解?什么时候可以依赖推断?
6.1 类型推断
TypeScript 能根据上下文自动推断类型:
// 变量初始化时的推断
const name = "张三" // 推断为 string
const age = 25 // 推断为 number
const isActive = true // 推断为 boolean
// 数组推断
const numbers = [1, 2, 3] // 推断为 number[]
const mixed = [1, "hello", true] // 推断为 (number | string | boolean)[]
// 函数返回值推断
function add(a: number, b: number) {
return a + b // 推断返回值为 number
}👇 动手试试看:观察 TypeScript 如何推断类型
🔮 类型推断演示
什么是类型推断?
TypeScript 很聪明,它能根据你写的代码自动推断出变量的类型,不需要每次都手动标注。
选择一个示例看看类型推断是如何工作的:
let name = "张三"📚 最佳实践
- 变量初始化时有明确的值
- 函数返回值可以明显推断
- 简单的字面量赋值
- 函数参数(必须)
- 对象或数组的复杂结构
- 无法从初始值推断类型
- 需要明确的类型约束
🔄 类型推断 vs 显式注解
function add(a: number, b: number) {
return a + b // 推断为 number
}function add(a: number, b: number): number {
return a + b
}const user = {
name: "张三",
age: 25,
email: "test@example.com"
} // 类型自动推断interface User {
name: string
age: number
email: string
}
const user: User = { ... }6.2 何时使用显式类型注解
推荐使用类型推断的场景
// ✅ 推荐:简单的字面量赋值
const count = 0
const name = "张三"
const isActive = true
// ✅ 推荐:函数返回值可以推断
function getUserId(user: User) {
return user.id // 推断为 number
}推荐使用显式注解的场景
// ✅ 推荐:函数参数(必须)
function add(a: number, b: number) {
return a + b
}
// ✅ 推荐:对象属性类型不明确
const user: {
id: number
name: string
metadata: Record<string, any>
} = {
id: 1,
name: "张三",
metadata: {} // 可能推断为 {},需要明确指定
}
// ✅ 推荐:函数返回类型复杂
function getUser(): User | null {
// ...
return null
}
// ✅ 推荐:公共 API
export function calculateTotal(prices: number[]): number {
return prices.reduce((sum, price) => sum + price, 0)
}6.3 类型守卫
在运行时检查类型:
// typeof 类型守卫
function processValue(value: string | number) {
if (typeof value === "string") {
// 这里 TypeScript 知道 value 是 string
console.log(value.toUpperCase())
} else {
// 这里 TypeScript 知道 value 是 number
console.log(value * 2)
}
}
// instanceof 类型守卫
class Dog {
bark() {
console.log("汪汪")
}
}
class Cat {
meow() {
console.log("喵喵")
}
}
function makeSound(animal: Dog | Cat) {
if (animal instanceof Dog) {
animal.bark() // TypeScript 知道这是 Dog
} else {
animal.meow() // TypeScript 知道这是 Cat
}
}
// 自定义类型守卫
interface User {
name: string
email: string
}
function isUser(value: any): value is User {
return (
typeof value === "object" &&
value !== null &&
typeof value.name === "string" &&
typeof value.email === "string"
)
}
function processValue(value: unknown) {
if (isUser(value)) {
// 这里 value 是 User
console.log(value.name)
}
}6.4 实用工具类型
TypeScript 提供了一些内置的工具类型:
// Partial:将所有属性变为可选
interface User {
id: number
name: string
email: string
}
type PartialUser = Partial<User>
// 等价于:{ id?: number; name?: string; email?: string }
// Required:将所有属性变为必需
type RequiredUser = Required<PartialUser>
// 等价于:{ id: number; name: number; email: string }
// Pick:只保留指定的属性
type UserBasicInfo = Pick<User, "id" | "name">
// 等价于:{ id: number; name: string }
// Omit:排除指定的属性
type UserWithoutEmail = Omit<User, "email">
// 等价于:{ id: number; name: string }
// Record:创建对象类型
type UserRoles = Record<string, boolean>
// 等价于:{ [key: string]: boolean }7. 实战技巧:在 vibecoding 中使用 TypeScript
🤔 核心问题
怎么在 AI 辅助开发中更好地利用 TypeScript?
7.1 让 AI 生成类型安全代码
❌ 不好的提示词:
帮我写一个用户管理功能✅ 好的提示词:
帮我写一个用户管理功能,使用 TypeScript。
数据结构定义如下:
interface User {
id: number
name: string
email: string
age: number
}
需要实现:
1. 获取用户列表:返回 User[]
2. 创建用户:接受 Partial<User>,返回 User
3. 更新用户:接受 id 和 Partial<User>,返回 User
4. 删除用户:接受 id,返回 void
请确保所有函数都有完整的类型注解。7.2 看懂 TypeScript 错误信息
常见错误及含义:
| 错误信息 | 含义 | 解决方法 |
|---|---|---|
Type 'X' is not assignable to type 'Y' | 类型 X 不能赋值给类型 Y | 检查类型是否匹配,或进行类型转换 |
Property 'X' does not exist on type 'Y' | 类型 Y 上不存在属性 X | 检查属性名拼写,或定义该属性 |
Argument of type 'X' is not assignable to parameter of type 'Y' | 参数类型不匹配 | 检查函数调用时的参数类型 |
Type 'X' is missing the following properties from type 'Y' | 类型 X 缺少类型 Y 的某些属性 | 补全缺失的属性 |
7.3 渐进式采用 TypeScript
如果你有一个 JavaScript 项目,可以渐进地迁移到 TypeScript:
第一步:将文件重命名为
.tsbash# 从 utils.js 改为 utils.ts mv utils.js utils.ts第二步:修复明显的类型错误
typescript// 如果报错:Parameter 'a' implicitly has an 'any' type // 添加类型注解 function add(a: number, b: number) { return a + b }第三步:逐步添加类型定义
typescript// 先用 any 快速修复 function processUser(user: any) { // ... } // 后续再完善类型 interface User { id: number name: string } function processUser(user: User) { // ... }第四步:启用更严格的类型检查
json// tsconfig.json { "compilerOptions": { "strict": true, // 启用严格模式 "noImplicitAny": true, // 禁止隐式 any "strictNullChecks": true // 严格空值检查 } }
8. 你现在应该能识别的代码
- 看到
: string→ 这是 string 类型的注解 - 看到
: number[]→ 这是数字数组的注解 - 看到
interface User→ 这是定义对象类型 - 看到
type User =→ 这是类型别名 - 看到
<T>→ 这是泛型 - 看到
extends→ 接口继承或泛型约束 - 看到
?→ 可选属性 - 看到
readonly→ 只读属性 - 看到
|→ 联合类型 - 看到
&→ 交叉类型
如果你认真读了每章的"深入"部分,你还掌握了这些核心概念:
- 类型注解:明确告诉 TypeScript 变量的类型
- 接口:定义对象的结构和类型
- 泛型:编写可复用的类型安全代码
- 类型推断:TypeScript 自动推断类型
- 类型守卫:运行时检查类型
- 工具类型:Partial、Required、Pick、Omit 等
💡 遇到问题时这样跟 AI 说
- "这个函数的类型注解应该怎么写?参数是 X,返回值是 Y"
- "帮我定义一个接口,描述这个数据结构:..."
- "这个 TypeScript 错误是什么意思?怎么修复?"
- "如何给这个泛型函数添加约束,确保 T 必须有某个属性?"
