手把手带你实现类型<LookUp>

251 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第12天,点击查看活动详情

如何使用

<LookUp>类型能够根据某个属性在联合类型中查找对应的类型。

例如:我们想通过在联合类型Cat | Dog中搜索公共type字段来获取相应的类型。换句话说,在以下示例中,我们期望LookUp<Dog | Cat, 'dog'>获得DogLookUp<Dog | Cat, 'cat'>获得Cat

interface Cat {
  type: 'cat'
  breeds: 'Abyssinian' | 'Shorthair' | 'Curl' | 'Bengal'
}

interface Dog {
  type: 'dog'
  breeds: 'Hound' | 'Brittany' | 'Bulldog' | 'Boxer'
  color: 'brown' | 'white' | 'black'
}

type MyDog = LookUp<Cat | Dog, 'dog'> // expected to be `Dog`

如何实现

主要实现思路还是利用extends分布式条件类型进行判断。

type LookUp<U, T extends keyof U> = U extends {type: T} ? U : never
  • T extends keyof U进行类型约束,简单的说就是缩小T的取值范围,让错误在更早的阶段暴露出来
  • 利用extends分布式条件类型进行判断:U是否符合形如{type: T}的数据结构

再谈 extends 条件类型的用法

这里extends的类型约束以及条件类型的用法都在extends关键字中提到过, 类型约束的用法大家应该都很熟悉了,这里再将条件类型的一些注意点复习一下:

条件类型的常见表现形式为:

 T extends U ? 'Y' : 'N'

要注意:

  • extends在条件类型中的作用和类型约束中的作用不一样
  • 条件类型只支持在type中使用
  • extends作为条件类型时也是可以嵌套的,就像if语句一样。
type TypeName<T> =
 T extends string    ? "string" :
 T extends number    ? "number" :
 T extends boolean   ? "boolean" :
 T extends undefined ? "undefined" :
 T extends Function  ? "function" :
 "object";

 type T0 = TypeName<string>;  // "string"
 type T1 = TypeName<"a">;     // "string"
 type T2 = TypeName<true>;    // "boolean"
 type T3 = TypeName<() => void>;  // "function"
 type T4 = TypeName<string[]>;    // "object"

关于分配条件类型要注意:

1. extends关键字左侧的是一个泛型,且传入泛型的必须是联合类型,其他类型如交叉类型是没有分配效果的。

如果左侧不是泛型,直接传入一个联合类型,是没有分配效果的,只是一个简单的条件判断。

  type A1 = 'x' extends 'x' ? string : number; // string

  type A2 = 'x' | 'y' extends 'x' ? string : number; // number
// 如果分配生效的话,结果应该是 string | number

2. 分配操作只有在检查的类型是naked type parameter时才生效。

那么是什么是naked type parameter呢?直接翻译过来怪怪的,参数是的? 我的理解是没有对传进来的泛型参数进行一些额外操作,那么就符合naked type parameter的要求。

看一下以下的例子,更容易理解。这也是stackoverflow上一个高赞回答的例子

type NakedUsage<T> = T extends boolean ? "YES" : "NO"
type WrappedUsage<T> = [T] extends [boolean] ? "YES" : "NO"; // wrapped in a tuple,type Distributed = NakedUsage<number | boolean > // = NakedUsage<number> | NakedUsage<boolean> =  "NO" | "YES" 
type NotDistributed = WrappedUsage<number | boolean > // "NO"    
type NotDistributed2 = WrappedUsage<boolean > // "YES"

其中,WrappedUsage对传入的泛型参数进行了操作,不属于naked type parameter,因此不会进行分配操作。

更多