三年前接手一个遗留JavaScript项目时,我遇到了典型的"动态类型恐惧症"——一个对象在不同文件中被随意修改属性,调试时像在玩大家来找茬。这正是TypeScript要解决的核心痛点:为JavaScript添加静态类型检查。不同于其他转译语言,TypeScript不是新语言,而是JS的超集,这意味着任何合法的JS代码都是合法的TS代码。
类型系统带来的优势在实际开发中尤为明显。上周团队新人提交的PR中,TypeScript编译器直接拦截了15处潜在的类型错误,包括经典的undefined访问和属性拼写错误。配合VSCode的智能提示,开发者能实时获得类型反馈,这种开发体验的提升是革命性的。
TypeScript的基础类型包括number、string、boolean等,但有趣的是其类型推断机制。声明变量时如果直接赋值,类型注解往往可以省略:
typescript复制let age = 30 // 自动推断为number
age = "30" // 报错:不能将string赋值给number
对于复杂对象,我推荐使用interface而非type alias定义形状。interface的声明合并特性在处理第三方库类型扩展时非常实用:
typescript复制interface User {
name: string
}
interface User {
age?: number // 自动合并为{ name: string; age?: number }
}
联合类型和类型守卫是处理业务逻辑的利器。在电商项目中,商品可能有不同的折扣策略:
typescript复制type Discount =
| { kind: 'percent'; value: number }
| { kind: 'fixed'; value: number }
function applyDiscount(price: number, discount: Discount) {
if (discount.kind === 'percent') {
return price * (1 - discount.value / 100)
} else {
return price - discount.value
}
}
泛型的使用能极大提升代码复用性。我在封装API请求函数时这样设计返回类型:
typescript复制async function fetchData<T>(url: string): Promise<T> {
const res = await fetch(url)
return res.json()
}
// 使用时明确指定返回类型
const user = await fetchData<User>('/api/user')
一个合理的tsconfig.json应该平衡类型检查严格度和开发效率。我常用的严格模式配置:
json复制{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"moduleResolution": "NodeNext",
"esModuleInterop": true,
"skipLibCheck": true
}
}
关键提示:在大型项目中启用"strict": true虽然初期痛苦,但能避免90%的类型相关bug
与Vite的集成示例:
bash复制npm create vite@latest my-app --template vanilla-ts
在React项目中,组件props的类型定义应该这样处理:
typescript复制interface ButtonProps {
size?: 'small' | 'medium' | 'large'
onClick: () => void
}
const Button: React.FC<ButtonProps> = ({ size = 'medium', onClick }) => {
// 组件实现
}
遇到没有类型声明的老库时,可以在项目根目录创建types/xxx.d.ts:
typescript复制declare module 'legacy-library' {
export function doSomething(config: {
timeout?: number
retry?: boolean
}): Promise<void>
}
我习惯按功能模块组织类型定义:
code复制src/
types/
user.ts # 用户相关类型
product.ts # 商品相关类型
api.d.ts # API响应类型
对于API响应,使用泛型统一包装:
typescript复制interface ApiResponse<T> {
code: number
data: T
message?: string
}
在monorepo项目中,使用项目引用(project references)可以显著提升编译速度:
json复制// tsconfig.base.json
{
"compilerOptions": {
"composite": true,
"incremental": true
}
}
// packages/client/tsconfig.json
{
"references": [{ "path": "../shared" }]
}
遇到复杂的类型错误时,可以使用类型提取:
typescript复制type DebugType<T> = T extends infer U ? { [K in keyof U]: U[K] } : never
// 查看推导出的类型
type Revealed = DebugType<SomeComplexType>
在团队协作中,我制定的TypeScript评审清单包括:
将现有JS项目迁移到TS的步骤:
在混合代码库中,可以使用JSDoc注释逐步引入类型检查:
javascript复制// @ts-check
/**
* @param {string} username
* @returns {Promise<User>}
*/
async function fetchUser(username) {
// 实现
}
当两个库声明了同名类型时,可以使用import别名:
typescript复制import { Button as AButton } from 'antd'
import { Button as MButton } from 'material-ui'
安全地访问嵌套对象属性:
typescript复制function safeGet<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key]
}
// 比obj[key]更安全,因为key会被校验
实现路由参数类型校验:
typescript复制type Route = `/user/${string}/post/${number}`
function navigate(path: Route) {
// 实现
}
navigate('/user/123/post/456') // 正确
navigate('/user//post/abc') // 报错
在保持类型推断的同时进行约束:
typescript复制const theme = {
colors: {
primary: '#1890ff',
error: '#ff4d4f'
}
} satisfies Record<string, { primary: string; error: string }>
// theme.colors. 输入时会自动提示primary和error