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 不能保证程序一定正确,但它能在程序运行之前,把大量错误暴露出来。