TypeScript 内置 utility types 速查 + 自己造的几个常用

TypeScript 的标准库带了几十个 utility type。熟练用能少写一半重复定义。
下面只挑实战频率最高的 20+ 个 + 5 个我自己常造的。

内置常用

Partial<T> / Required<T>

interface User { id: string; name: string; email: string }

// 所有字段可选(PATCH 接口最常用)
function update(id: string, patch: Partial<User>) {}
update('1', { name: 'Alice' })   // OK

// 所有字段必选
type FullUser = Required<Partial<User>>

Pick<T, K> / Omit<T, K>

type UserPublic = Pick<User, 'id' | 'name'>           // 只要这些字段
type UserPrivate = Omit<User, 'email' | 'password'>   // 去掉这些字段

Record<K, V>

type StatusMap = Record<'pending' | 'done' | 'failed', number>
// { pending: number; done: number; failed: number }

type Cache<T> = Record<string, T>   // 字典

Readonly<T> / ReadonlyArray<T>

function process(items: ReadonlyArray<string>) {
  items.push('x')  // ❌ 编译错
}

ReturnType<F> / Parameters<F>

function fetchUser(id: string): Promise<User> { ... }

type UserResp = Awaited<ReturnType<typeof fetchUser>>   // User
type Args = Parameters<typeof fetchUser>                // [string]

Awaited<T> 解 Promise 包装。

Exclude<T, U> / Extract<T, U>

type Color = 'red' | 'green' | 'blue' | 'transparent'
type Opaque = Exclude<Color, 'transparent'>            // 'red' | 'green' | 'blue'
type Warm = Extract<Color, 'red' | 'orange'>           // 'red'

NonNullable<T>

type StringOrNull = string | null | undefined
type DefinitelyString = NonNullable<StringOrNull>      // string

Awaited<T>

type A = Awaited<Promise<Promise<number>>>             // number

递归解包 Promise。

自定义常用

DeepPartial<T> — 递归 Partial

type DeepPartial<T> = T extends object
  ? { [K in keyof T]?: DeepPartial<T[K]> }
  : T

type Config = { a: { b: { c: number } } }
type PartialConfig = DeepPartial<Config>
// { a?: { b?: { c?: number } } }

适合 patch 嵌套对象。

DeepReadonly<T>

type DeepReadonly<T> = T extends object
  ? { readonly [K in keyof T]: DeepReadonly<T[K]> }
  : T

PickRequired<T, K> — 指定字段必选,其它保持

type PickRequired<T, K extends keyof T> = T & { [P in K]-?: T[P] }

interface Opts { name?: string; age?: number; email?: string }
type WithName = PickRequired<Opts, 'name'>
// { name: string; age?: number; email?: string }

-? 是 "去掉 optional 修饰"。

Branded<T, B> — 名义类型(防混淆)

type Branded<T, B extends string> = T & { __brand: B }
type UserId = Branded<string, 'UserId'>
type PostId = Branded<string, 'PostId'>

function getUser(id: UserId): User { ... }

const uid = 'u1' as UserId
const pid = 'p1' as PostId
getUser(uid)   // OK
getUser(pid)   // ❌ Type 'PostId' is not assignable to 'UserId'

让基础类型(string / number)按业务含义区分,编译期阻止误用。

ValueOf<T>

type ValueOf<T> = T[keyof T]

const Status = { Active: 'active', Inactive: 'inactive' } as const
type StatusVal = ValueOf<typeof Status>   // 'active' | 'inactive'

Mutable<T> — 反向 Readonly

type Mutable<T> = { -readonly [K in keyof T]: T[K] }

XOR<T, U> — 两选一不能同时

type Without<T, U> = { [P in Exclude<keyof T, keyof U>]?: never }
type XOR<T, U> = (Without<T, U> & U) | (Without<U, T> & T)

type Login = XOR<{ email: string }, { phone: string }>
// 必须传 email 或 phone,不能两个都传

高级:条件类型 + infer

// 提取数组元素类型
type ArrayElement<T> = T extends (infer E)[] ? E : never
type Item = ArrayElement<string[]>   // string

// 提取函数返回类型(重新实现 ReturnType)
type MyReturnType<F> = F extends (...args: any[]) => infer R ? R : never

// 提取 Promise 解析后类型(重新实现 Awaited)
type Unpromise<T> = T extends Promise<infer V> ? V : T

infer 让你在条件类型里"声明并捕获"一个类型变量。

字符串字面量类型

type CSSVar<S extends string> = `--${S}`
type X = CSSVar<'primary'>   // "--primary"

type ToCamelCase<S extends string> = S extends `${infer A}-${infer B}${infer Rest}`
  ? `${A}${Uppercase<B>}${ToCamelCase<Rest>}`
  : S
type C = ToCamelCase<'foo-bar-baz'>   // "fooBarBaz"

模板字面量类型 + infer 让 TS 能做一些"字符串编程"。但别滥用——
复杂的会让编辑器卡 + 错误信息难懂。

类型守卫

function isError(x: unknown): x is Error {
  return x instanceof Error
}

try { ... } catch (e) {
  if (isError(e)) {
    console.error(e.message)   // TS 知道 e 是 Error
  }
}

x is Type 让 TS 在 if 分支里 narrow 类型。

type vs interface 何时选

  • 简单对象 / 可被扩展(库提供的):interface
  • 联合 / 交叉 / 字面量 / 复杂条件:type
  • 不知道选哪个:type(更通用)

性能上几乎无差。

工具:type-fest

type-fest 收集了 100+ 实用
utility type:

npm i type-fest
import type { SetRequired, JsonValue, CamelCase } from 'type-fest'

type User = { name?: string; age?: number }
type WithName = SetRequired<User, 'name'>

不要每个项目都自己实现 DeepPartial,引这个库直接用。

踩过的坑

  • Partial<DeepFoo> 不递归。要 DeepPartial<...>
  • Exclude<T, U> 看似"减去",但 U 必须是 T 的子集;不是的话什么都不去。
  • as Type 强制断言不安全(编译器信你的)。能用类型守卫就用。
  • 复杂 conditional type 报错时 TS 错误信息可达几十行。把复杂 type 拆
    成中间命名 type,错误位置更清楚。
精确评价 共 0 人评价
可复现性
可复现 · 0 不可复现 · 0
文风
文风流畅 · 0 文风晦涩 · 0
立场
支持 · 0 反对 · 0

登录后即可对本帖作出评价。

评论区 0 条 · 所有人可在此交流

登录后参与评论。

还没有评论,来说两句。