常用工具类型

2023/12/12 TypeScript

# 过滤可选属性

// 获取可选属性
type GetOptional<T extends Record<PropertyKey, any>> = {
  [K in keyof T as T[K] extends Required<T>[K] ? never : K]: T[K]
}
// 获取必选属性
type GetReqiured<T extends Record<PropertyKey, any>> = {
  [K in keyof T as T[K] extends Required<T>[K] ? K : never]: T[K]
}
1
2
3
4
5
6
7
8

解析:对于T[K] extends Required<T>[K],如果当前K是可选属性,T[K]类型为string | undefined,那么Required<T>[K]的类型为stringstring类型的值可以赋值给string | undefined,反之则不行。

泛型约束:T extends Record<PropertyKey, any>,表示传入的只能是对象类型(anynever也可以)。

另外实现方式:

type GetOptional<T extends Record<PropertyKey, any>> = Pick<T, Exclude<{
  [K in keyof T]: T extends Record<K, T[K]> ? never : K
}[keyof T], undefined>>

type GetReqiured<T extends Record<PropertyKey, any>> = Pick<T, Exclude<{
  [K in keyof T]: T extends Record<K, T[K]> ? K : never
}[keyof T], undefined>>
1
2
3
4
5
6
7

解析:

  1. K in keyof T遍历类型T的所有键,通过Record来单独标记每个K的类型,Record<K, T[K],例如name得到的是{ name: string }

  2. 然后通过条件类型extends判断传入的泛型T能不能赋值给Record生成的类型。

    interface Person {
      name: string;
      age: number;
      sex?: string;
      job?: string;
    }
    type P1 = {
      name: string;
    }
    type P2 = {
      job: string;
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12

    Record生成的都是必选属性,所以Person的实例无法赋值给P2的实例(string | undefined不能分配给string),即可选属性生成的类型,此时有条件选择变成never

  3. 然后通过keyof T取值,[K in keyof T]: T extends Record<K, T[K]> ? K : never }[keyof T]得到的最终就是必选属性组成的联合类型,此处为'name' | 'age'

  4. 通过Exclude剔除其中的undefined

  5. 最终在类型T中选出必选属性组成新的类型,达到过滤属性目的。

# 判断两个类型是否相等

相等判断:

type Equal<X, Y> = (<T>() => T extends X ? 1 : 2) extends (<T>() => T extends Y ? 1 : 2) ? true : false
1

如果有两个条件类型T1 extends U1 ? X1 : Y1T2 extends U2 ? X2 : Y2,它们被认为是相关的,需要满足以下条件:

  1. T1T2中的一个与另一个相关。这意味着T1可以赋值给T2或者T2可以赋值给T1
  2. U1U2是相同的类型。
  3. X1可以赋值X2
  4. Y1可以赋值Y2

上面Equal类型中,两个函数的泛型T都没有类型约束(也可以加上相同的类型约束,效果是一样的),说明两个是可以互相赋值的,因此如果需要满足第一个条件类型可以赋值给第二个条件类型,必须满足剩余条件2,即XY两个类型必须相等。

microsoft/src/compiler/checker.ts (opens new window)

不相等判断:

type NotEqual<X, Y> = true extends Equal<X, Y> ? false : true
1

# any类型判断

// https://stackoverflow.com/questions/49927523/disallow-call-with-any/49928360#49928360
type IsAny<T> = 0 extends 1 & T ? true : false
// type IsAny<T> = number extends T & string ? true : false

// 不是any
type NotAny<T> = true extends IsAny<T> ? false : true
1
2
3
4
5
6

0 extends 1不满足类型约束(0不可分配给1),因此0 extends (1 & T)也不满足,因为(1 & T)应该比1更窄。然而,当Tany时,0 extends (1 & any)等价于0 extends any,这是满足的,这是因为any是故意不健全的,并且充当几乎所有其他类型的超类型和子类型。

type IsAny1<T> = T extends any ? true : false;
// 所有值都返回true

type IsAny2<T> = any extends T ? true : false;
// any返回true,但是T对于其他类型,any可以是任何东西,无法决定返回哪一个,所以最后返回boolean
1
2
3
4
5

# never类型判断

type IsNever<T> = [T] extends [never] ? true : false
1
// https://github.com/microsoft/TypeScript/issues/31751
type MakesSense = never extends never ? 'yes' : 'no' // Resolves to 'yes'

type ExtendsNever<T> = T extends never ? 'yes' : 'no'

type MakesSenseToo = ExtendsNever<{}> // Resolves to 'no'
type Huh = ExtendsNever<never> // Expect to resolve to 'yes', actually resolves to never
1
2
3
4
5
6
7

ExtendsNever是分配条件类型,条件类型分布在联合类型上。如果T是一个联合类型,ExtendsNever则将其应用于联合的每个成员,结果是所有应用程序的联合(ExtendsNever<'a' | 'b'> == ExtendsNever<'a' > | ExtendsNever<'b'>)。never是空联合类型(即没有成员的联合)。这是通过它在联合类型'a' | never == 'a'中的行为暗示了这一点。因此,当分配到never时,ExtendsNever永远不会应用,因为该联合类型中没有成员,因此结果为never。而[T]则可以禁止联合类型的分发。

# JSON.parse类型推断

JSON对象提供类型重载:

// type JSONString<T> = string & T;
interface JSONString<T> extends String {
  _: never;
}

interface JSON {
  parse<T>(text: JSONString<T>, reviver?: (this: any, key: string, value: any) => any): T;
  stringify<T>(value: T, replacer?: (this: any, key: string, value: any) => any, space?: string | number): JSONString<T>;
  stringify<T>(value: T, replacer?: (number | string)[] | null, space?: string | number): JSONString<T>;
}
1
2
3
4
5
6
7
8
9
10

如果JSONString定义为type JSONString<T> = string & T;,会存在直接给JSONString赋值字符串,导致parse操作得到unknown类型。

最近更新: 2025年03月02日 21:14:11