typescript的高级类型

TypeScript 重点速记(7 个模块)

0. 心法(先建立直觉)

  • 类型 = 集合:A | B 并集,A & B 交集,never 空集,unknown 全集,any 关闭检查。
  • TS 用的是结构化类型(structural typing):只看形状,不看名字。
  • 类型体操 = 三板斧:条件类型(分支)+ 映射(遍历)+ infer(提取)。

1. 高级类型

// 联合 + 字面量
type Status = 'todo' | 'done' | 'archived'
type Id = string | number

// 判别式联合(实战最常用,配合 switch 自动收窄)
type Shape =
  | { kind: 'circle'; r: number }
  | { kind: 'rect'; w: number; h: number }

function area(s: Shape) {
  switch (s.kind) {            // 靠 kind 收窄
    case 'circle': return Math.PI * s.r ** 2   // 此处 s 已是 circle
    case 'rect':   return s.w * s.h
  }
}

类型收窄手段:typeof / in / instanceof / 判别式字段 / 自定义类型守卫 function isX(v: unknown): v is X。

条件类型 + infer

type IsStr<T> = T extends string ? true : false
type Item<T> = T extends Array<infer U> ? U : T      // infer 提取
type Ret<F> = F extends (...a: any[]) => infer R ? R : never

// 分发特性:裸类型参数遇联合会分发
type A = Item<string[] | number>  // string | number
// 阻止分发:[T] extends [U] ? X : Y

常用内置工具(背下来)
Partial / Required / Readonly / Pick / Omit / Record / Exclude / Extract / NonNullable / ReturnType / Parameters / Awaited


2. class 类

class Person {
  public name: string            // public 可省
  private secret = 1             // 仅本类
  protected age = 18             // 本类 + 子类
  readonly id: string            // 只读
  static count = 0               // 静态,属于类本身

  constructor(name: string, public email: string) {  // 参数属性:一行声明并赋值
    this.name = name
  }
  get upper() { return this.name.toUpperCase() }     // getter
}

关键区分:

  • Person = 实例类型(描述 new 出来的对象)
  • typeof Person = 构造器类型(描述类本身,含 static 成员)
function create(Ctor: new (name: string) => Person) { return new Ctor('a') }
// 或:typeof Person

其他:abstract 抽象类/方法(不能直接 new,子类必须实现);implements 实现接口(只约束形状,不继承实现);类可以同时 extends 一个类并 implements 多个接口。


3. 类型兼容性(最容易踩坑)

对象:属性多 → 赋给 属性少,OK(多的被忽略);反之不行。

type A = { a: number }
type B = { a: number; b: string }
let b: B = { a: 1, b: 'x' }
let a: A = b          // ✅
// b = a              // ❌ 缺 b

⚠️ 多余属性检查:只有对象字面量直接赋值才报错,变量中转不报错。

let a2: A = { a: 1, b: 'x' }   // ❌ 字面量多属性
let tmp = { a: 1, b: 'x' }; let a3: A = tmp   // ✅

绕开:类型断言 as A 或加索引签名。

函数:

  • 参数个数:少的可以赋给多的(少参数函数能安全替代)。
  • 返回值:协变(子类型返回值 OK)。
  • 参数类型:strictFunctionTypes: true 下逆变(参数更宽才安全);方法(method 简写)是双变。
type F1 = (x: string | number) => void
type F2 = (x: string) => void
let f1: F1 = (x) => {}
let f2: F2 = f1       // ✅ 参数更宽 → 更窄

其它:private/protected 成员存在时,只有同一声明来源的类才兼容;枚举与 number 不互相兼容(数字枚举除外)。


4. 交叉类型 &

type A = { a: string }
type B = { b: number }
type C = A & B        // { a: string; b: number },必须同时满足
  • 对象交叉 = 属性合并;同名属性会再取交集:{x: string} & {x: number} → x: string & number = never(易错!)
  • 联合 + 交叉有分配律:(A|B) & C = (A&C)|(B&C)
  • interface extends vs type &:extends 同名成员冲突直接报错;& 不报错而是变成交集(可能静默变 never)。

5. 泛型 + keyof

function identity<T>(v: T): T { return v }

// 约束 & 默认
function getProp<T extends object, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key]      // 键安全 + 返回值类型精确
}
getProp({ a: 1, b: 'x' }, 'a')   // number
// getProp({ a: 1 }, 'c')        // ❌ 编译期报错

keyof 要点:

interface P { name: string; age: number }
type Keys = keyof P                 // 'name' | 'age'
type Keys2 = keyof P & string       // 只要字符串键
keyof any                            // string | number | symbol
keyof []                             // number | 'length' | 'push' | ...

泛型常见位置:函数、接口 interface Api<T>、类型别名 type Res<T> = { data: T }、类 class Repo<T>、约束 extends、默认 <T = string>。


6. 索引签名类型 + 索引查询类型

// 索引签名:描述"未知键"的对象
interface Dict { [key: string]: number }
const d: Dict = { a: 1, b: 2 }

interface Nums { [index: number]: string }   // 数组式

规则/坑:

  • 一旦写了 [key: string]: X,所有具名属性的类型都必须是 X 的子类型(否则报"属性不兼容索引签名")。
  • Record<K, T> 就是索引签名的糖:Record<'a'|'b', number> = { a: number; b: number }。
  • js 中对象键最终都是 string/数字,用 Record<string, unknown> 比 object / {} 安全({} 几乎不校验)。
// 索引查询(访问)类型
type NameType = P['name']            // string
type Vals = P[keyof P]               // string | number
type Arr = string[]
type Elem = Arr[number]              // string  ← 数组元素惯用写法

7. 映射类型(类型体操主力)

type Mapped<T> = { [K in keyof T]: T[K] }   // 遍历键,原样复制

修饰符操作:

type Partial2<T>  = { [K in keyof T]?: T[K] }        // +? 可选
type Required2<T> = { [K in keyof T]-?: T[K] }       // -? 去可选
type Readonly2<T> = { readonly [K in keyof T]: T[K] }
type Mutable<T>   = { -readonly [K in keyof T]: T[K] }  // 去只读

键重映射(TS 4.1+,as)

type Getters<T> = { [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K] }
type FilterStr<T> = { [K in keyof T as T[K] extends string ? K : never]: T[K] }

手写核心工具(理解一次就通)

type Pick2<T, K extends keyof T> = { [P in K]: T[P] }
type Omit2<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>
type Record2<K extends keyof any, V> = { [P in K]: V }
type Exclude2<T, U> = T extends U ? never : T

速记口诀

  1. 收窄:判别式 + in/typeof + v is T。
  2. 兼容:对象"多给少"OK,字面量多属性报错,函数参数宽给窄 OK。
  3. 遍历:[K in keyof T],+?/-?、readonly/-readonly、as 改名/过滤。
  4. 提取:条件类型里用 infer;T['k']、Arr[number]、T[keyof T]。
  5. 冲突:交叉同名属性 → never;索引签名要求所有属性兼容它。
posted @ 2026-10-12 16:48  寅信  阅读(4)  评论(0)    收藏  举报