TypeScript 高级类型技巧:提升代码质量
作者:君知博客团队
48 分钟阅读
#TypeScript#类型系统#泛型#类型安全#最佳实践
TypeScript 高级类型技巧:提升代码质量
TypeScript 的类型系统非常强大,掌握高级类型技巧可以让你的代码更加安全、可维护。本文将介绍一些实用的高级类型特性。
1. 泛型(Generics)
泛型是 TypeScript 中最强大的特性之一,它允许我们编写可重用的代码。
基础泛型
// 简单的泛型函数
function identity<T>(arg: T): T {
return arg
}
// 使用
const result1 = identity<string>("hello") // 类型: string
const result2 = identity(42) // 类型推断: number
泛型约束
// 约束泛型必须有 length 属性
interface Lengthwise {
length: number
}
function logLength<T extends Lengthwise>(arg: T): T {
console.log(arg.length)
return arg
}
logLength("hello") // ✅ 字符串有 length
logLength([1, 2, 3]) // ✅ 数组有 length
logLength({ length: 10, value: 3 }) // ✅ 对象有 length
// logLength(42) // ❌ 数字没有 length
泛型接口
// API 响应的通用接口
interface ApiResponse<T> {
data: T
status: number
message: string
}
// 用户数据
interface User {
id: number
name: string
email: string
}
// 使用
async function fetchUser(id: number): Promise<ApiResponse<User>> {
const response = await fetch(`/api/users/${id}`)
return response.json()
}
// 文章列表
interface Post {
id: number
title: string
content: string
}
async function fetchPosts(): Promise<ApiResponse<Post[]>> {
const response = await fetch('/api/posts')
return response.json()
}
2. 条件类型(Conditional Types)
条件类型允许我们根据条件选择类型,类似于三元运算符。
基础条件类型
// T extends U ? X : Y
type IsString<T> = T extends string ? true : false
type A = IsString<string> // true
type B = IsString<number> // false
实用示例:提取函数返回类型
// 如果 T 是函数,提取返回类型;否则返回 never
type ReturnTypeOf<T> = T extends (...args: any[]) => infer R ? R : never
function getUser() {
return { id: 1, name: "张三" }
}
type UserType = ReturnTypeOf<typeof getUser>
// 类型: { id: number; name: string }
分布式条件类型
// 从联合类型中排除 null 和 undefined
type NonNullable<T> = T extends null | undefined ? never : T
type A = NonNullable<string | null | undefined> // string
type B = NonNullable<number | null> // number
3. 映射类型(Mapped Types)
映射类型允许我们基于旧类型创建新类型。
Partial - 所有属性变为可选
interface User {
id: number
name: string
email: string
}
// 内置的 Partial 实现
type MyPartial<T> = {
[P in keyof T]?: T[P]
}
type PartialUser = Partial<User>
// 等同于:
// {
// id?: number
// name?: string
// email?: string
// }
// 实际应用:更新用户信息
function updateUser(id: number, updates: Partial<User>) {
// 只需要传递要更新的字段
}
updateUser(1, { name: "李四" }) // ✅
updateUser(2, { email: "test@example.com" }) // ✅
Required - 所有属性变为必需
interface Config {
host?: string
port?: number
timeout?: number
}
type RequiredConfig = Required<Config>
// 等同于:
// {
// host: string
// port: number
// timeout: number
// }
Readonly - 所有属性变为只读
interface User {
id: number
name: string
}
type ReadonlyUser = Readonly<User>
// 等同于:
// {
// readonly id: number
// readonly name: string
// }
const user: ReadonlyUser = { id: 1, name: "张三" }
// user.name = "李四" // ❌ 错误:无法分配到只读属性
Pick - 选择部分属性
interface User {
id: number
name: string
email: string
password: string
createdAt: Date
}
// 只选择公开的用户信息
type PublicUser = Pick<User, 'id' | 'name' | 'email'>
// 等同于:
// {
// id: number
// name: string
// email: string
// }
Omit - 排除部分属性
// 排除敏感信息
type SafeUser = Omit<User, 'password'>
// 等同于:
// {
// id: number
// name: string
// email: string
// createdAt: Date
// }
4. 实用工具类型
Record - 创建对象类型
// Record<Keys, Type>
type PageInfo = {
title: string
url: string
}
type Pages = 'home' | 'about' | 'contact'
const pages: Record<Pages, PageInfo> = {
home: { title: '首页', url: '/' },
about: { title: '关于', url: '/about' },
contact: { title: '联系', url: '/contact' },
}
Extract - 提取联合类型的子集
type T1 = Extract<'a' | 'b' | 'c', 'a' | 'f'> // 'a'
type T2 = Extract<string | number | (() => void), Function> // () => void
Exclude - 排除联合类型的子集
type T1 = Exclude<'a' | 'b' | 'c', 'a' | 'b'> // 'c'
type T2 = Exclude<string | number | boolean, boolean> // string | number
5. 高级实战案例
案例 1:类型安全的事件系统
// 定义事件映射
interface EventMap {
'user:login': { userId: number; timestamp: Date }
'user:logout': { userId: number }
'post:create': { postId: number; title: string }
'post:delete': { postId: number }
}
// 类型安全的事件发射器
class TypedEventEmitter {
private listeners: {
[K in keyof EventMap]?: Array<(data: EventMap[K]) => void>
} = {}
// 监听事件
on<K extends keyof EventMap>(
event: K,
callback: (data: EventMap[K]) => void
) {
if (!this.listeners[event]) {
this.listeners[event] = []
}
this.listeners[event]!.push(callback)
}
// 触发事件
emit<K extends keyof EventMap>(event: K, data: EventMap[K]) {
const callbacks = this.listeners[event]
if (callbacks) {
callbacks.forEach(callback => callback(data))
}
}
}
// 使用
const emitter = new TypedEventEmitter()
// ✅ 类型正确
emitter.on('user:login', (data) => {
console.log(`用户 ${data.userId} 登录`)
})
// ✅ 类型正确
emitter.emit('user:login', {
userId: 1,
timestamp: new Date()
})
// ❌ 类型错误:缺少 timestamp
// emitter.emit('user:login', { userId: 1 })
案例 2:深度只读类型
// 递归地将所有属性变为只读
type DeepReadonly<T> = {
readonly [P in keyof T]: T[P] extends object
? DeepReadonly<T[P]>
: T[P]
}
interface Config {
database: {
host: string
port: number
credentials: {
username: string
password: string
}
}
cache: {
enabled: boolean
ttl: number
}
}
type ReadonlyConfig = DeepReadonly<Config>
const config: ReadonlyConfig = {
database: {
host: 'localhost',
port: 5432,
credentials: {
username: 'admin',
password: 'secret'
}
},
cache: {
enabled: true,
ttl: 3600
}
}
// ❌ 所有层级都是只读的
// config.database.host = 'newhost'
// config.database.credentials.password = 'newpass'
案例 3:类型安全的 API 客户端
// API 端点定义
interface ApiEndpoints {
'/users': {
GET: { response: User[] }
POST: { body: Omit<User, 'id'>; response: User }
}
'/users/:id': {
GET: { params: { id: number }; response: User }
PUT: { params: { id: number }; body: Partial<User>; response: User }
DELETE: { params: { id: number }; response: void }
}
'/posts': {
GET: { query: { page?: number; limit?: number }; response: Post[] }
POST: { body: Omit<Post, 'id'>; response: Post }
}
}
// 类型安全的 API 客户端
class ApiClient {
async request<
Path extends keyof ApiEndpoints,
Method extends keyof ApiEndpoints[Path]
>(
path: Path,
method: Method,
options?: ApiEndpoints[Path][Method] extends { body: infer B }
? { body: B }
: ApiEndpoints[Path][Method] extends { params: infer P }
? { params: P }
: ApiEndpoints[Path][Method] extends { query: infer Q }
? { query: Q }
: never
): Promise<
ApiEndpoints[Path][Method] extends { response: infer R } ? R : never
> {
// 实现细节...
return {} as any
}
}
// 使用
const api = new ApiClient()
// ✅ 类型安全
const users = await api.request('/users', 'GET')
// users 类型: User[]
// ✅ 类型安全
const newUser = await api.request('/users', 'POST', {
body: { name: '张三', email: 'zhang@example.com' }
})
// newUser 类型: User
// ❌ 类型错误:缺少必需的 body
// await api.request('/users', 'POST')
6. 最佳实践
1. 优先使用类型推断
// ❌ 不必要的类型注解
const name: string = "张三"
const age: number = 25
// ✅ 让 TypeScript 推断
const name = "张三" // 推断为 string
const age = 25 // 推断为 number
2. 使用 const 断言
// ❌ 类型过于宽泛
const config = {
host: 'localhost',
port: 3000
}
// config 类型: { host: string; port: number }
// ✅ 使用 const 断言
const config = {
host: 'localhost',
port: 3000
} as const
// config 类型: { readonly host: "localhost"; readonly port: 3000 }
3. 避免使用 any
// ❌ 失去类型安全
function processData(data: any) {
return data.value
}
// ✅ 使用泛型或 unknown
function processData<T>(data: T) {
return data
}
// 或者
function processData(data: unknown) {
if (typeof data === 'object' && data !== null && 'value' in data) {
return (data as { value: any }).value
}
}
4. 使用严格模式
// tsconfig.json
{
"compilerOptions": {
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"strictPropertyInitialization": true
}
}
总结
TypeScript 的高级类型系统为我们提供了强大的工具来编写类型安全的代码。关键要点:
- 善用泛型,编写可重用的代码
- 掌握条件类型,实现复杂的类型逻辑
- 活用映射类型,转换现有类型
- 使用工具类型,简化常见操作
- 遵循最佳实践,保持代码质量
通过这些技巧,你将能够构建更加健壮、可维护的 TypeScript 应用。
相关资源
标签: #TypeScript #类型系统 #泛型 #类型安全 #最佳实践
发布日期: 2025-02-08
作者: 君知博客团队