likes
comments
collection
share

TypeScript interface提取某个类型的属性名称

作者站长头像
站长
· 阅读数 69

问题

今天遇到一个TypeScript需求,以下是个interface:

interface A {
  a: string;
  b: number;
  c: boolean;
  d: number;
}

但我想取到类型全是number的属性名称,也就是相当于:

type X = 'b' | 'd'

答案

在官方的Pick、Extract一堆内置的方法里找了半天,没有适合的。于是查了半天资料,最终写出来是这样的:

type PickTypes<T, U> = {
  [K in keyof T]: T[K] extends U ? K : never;
}[keyof T];

使用:

type B = PickTypes<A, number>; 

const a: B = "a"; // 报错
console.log(a); 
const b: B = 'b'; // ok

const arr: B[] = ["a", "b"]; // 报错
console.log(arr);

在编辑里光标移动后B,会看到类型被正确推断出来 TypeScript interface提取某个类型的属性名称

解释

type PickTypes<T, U> = {
  [K in keyof T]: T[K] extends U ? K : never;
}[keyof T];

将这段代码拆分成两部分来理解:

type Temp<T, U> = {
  [K in keyof T]: T[K] extends U ? K : never;
}

type PickTypes<T, U> = type Temp<T, U>[keyof T]

第一部分就是对传递的参数T进行遍历,如果值是继承自U,则将该值设置成属性K,否则是never。 仍以上面的接口A为例,推导后的类型为: TypeScript interface提取某个类型的属性名称

第二部分是在上一部分的结果,再取下属性值。如果不好理解,可以先看这个推断:

type ValueTypes = A[keyof A];

TypeScript interface提取某个类型的属性名称 相当于

type ValueTypes = A['a'] | A['b'] | A['c'] | A['d'] 
                = string | number | boolean | number
                = string | number | boolean

而对于我们这个场景,就是

type PickTypes<T, U> = test['a'] | test['b'] | test['c'] | test['d']
                     = never | 'b' | never | 'd'
                     = 'b' | 'd'

扩展

假设我们的接口有个为undefined的类型,那就推断不出来了。例如:

interface A {
  a: string;
  b: number;
  c: boolean;
  d?: number;
}

那么怎么办呢?

两种方式,一种是在调用PickTypes前,先将参数Required,比如:

PickTypes<Required<A>, number>

这种肯定不够优雅,如果PickTypes就能做到就最好了,所以第二种方式最终代码如下:

type PickTypes<T, U> = NonNullable<
  {
    [K in keyof T]: NonNullable<T[K]> extends U ? K : never;
  }[keyof T]
>;

这里利用了NonNullable来去除undefined,其源码是这样的:

type NonNullable<T> = T extends null | undefined ? never : T

总结

TypesSript的类型系统设计的非常复杂,虽然高级的功能我们多数场景使用不到,但还是有必要深入理解学习的,尤其做底层框架或库开发时可以让你的代码更加健壮。

转载自:https://juejin.cn/post/7281491804737404982
评论
请登录