TypeScript 到底解决什么问题?
动态类型的代价
JavaScript 是动态类型:
function greet(name) {
return 'Hello, ' + name.toUpperCase()
}
greet('Alice') // ✅ "Hello, ALICE"
greet(42) // ❌ Runtime Error: name.toUpperCase is not a function
错误在运行时才暴露。
在小型项目里,这可以接受。但在大型项目里:
- 函数可能被几十个地方调用
- 传入的参数可能来自用户输入、API 响应、第三方库
- 错误可能在生产环境才暴露
静态类型检查:在运行前发现错误
function greet(name: string): string {
return 'Hello, ' + name.toUpperCase()
}
greet('Alice') // ✅
greet(42) // ❌ 编译错误:类型 'number' 不能赋值给类型 'string'
错误在编译时就暴露,不需要运行代码。
这就是 TypeScript 的核心价值:
把运行时错误,提前到编译时。
类型是编译时约束,不是运行时验证
一个常见误解:TypeScript 的类型会在运行时生效。
// 编译前
const x: number = 42
// 编译后
const x = 42
TypeScript 的类型只在编译时存在,编译后全部消失。这叫类型擦除。
所以 TypeScript 不能保证外部输入在运行时一定符合类型。
async function fetchUser(): Promise<User> {
const response = await fetch('/api/user')
return response.json() // 类型是 any,不是 User
}
fetch().json() 返回的是 any,TypeScript 无法知道 API 实际返回了什么。
外部数据需要运行时边界
外部数据进入 TypeScript 世界时,通常是不可信的:
- API 响应
- 用户输入
- localStorage
- URL 参数
- 第三方库
需要运行时验证来建立边界:
// 运行时验证函数
function isUser(data: unknown): data is User {
return (
typeof data === 'object' &&
data !== null &&
'name' in data &&
'age' in data &&
typeof (data as any).name === 'string' &&
typeof (data as any).age === 'number'
)
}
// 使用
async function fetchUser(): Promise<User> {
const response = await fetch('/api/user')
const data: unknown = await response.json()
if (isUser(data)) {
return data // TypeScript 知道 data 是 User
}
throw new Error('Invalid user data')
}
或者使用 Zod、Valibot 等 Schema Validation 库:
import { z } from 'zod'
const UserSchema = z.object({
name: z.string(),
age: z.number(),
})
type User = z.infer<typeof UserSchema>
async function fetchUser(): Promise<User> {
const response = await fetch('/api/user')
const data = await response.json()
return UserSchema.parse(data) // 运行时验证,失败则抛出错误
}
TypeScript 负责"代码内部的一致性",运行时验证负责"外部数据的可信性"。
TypeScript 与 V8 的关系
V8 在运行时优化:
- Hidden Class:给对象加结构描述
- Inline Cache:缓存属性访问路径
- TurboFan:生成优化机器码
但 V8 的优化有一个前提:代码要先跑起来,才能观察行为。
TypeScript 在编译时分析:
- 类型标注:告诉编译器"这是什么结构"
- 泛型约束:告诉编译器"这个函数接受什么类型"
- 条件类型:告诉编译器"根据输入决定输出类型"
不需要运行,编译时就能检查。
两者是互补关系:
- TypeScript 负责"代码写对"
- V8 负责"代码跑快"
核心洞察
TypeScript 主要通过静态分析,在程序运行之前缩小错误空间。它不是运行时优化工具,而是编译时约束工具。
TypeScript 不能保证程序一定正确,但它能在程序运行之前,把大量错误暴露出来。