简体   繁体   English

打字稿:使用条件映射键时获取正确的推断类型

[英]Typescript: Getting correct inference type when using conditional mapped keys

I'm trying to use Conditional Mapped types to get only allow keys of an object that are of a particular type as a parameter in a function. 我正在尝试使用条件映射类型来仅将具有特定类型的对象的键作为函数中的参数。

However, I'm running into an issue in that the correct type is not being inferred when I do so. 但是,我遇到了一个问题,当我这样做时,无法推断出正确的类型。

I've created an example to demonstrate ( view on typescript playground ): 我创建了一个示例来演示( 在打字稿操场上查看 ):

interface TraversableType{
  name: string;
}

interface TypeOne extends TraversableType{
  typeNotTraversable: string;
  typeTwo: TypeTwo;
  typeThree: TypeThree;
}

interface TypeTwo extends TraversableType{
  typeTwoNotTraversable: string;
  typeOne: TypeOne;
  typeThree: TypeThree;
}

interface TypeThree extends TraversableType{
  typeThreeProp: string;
}


type TraversablePropNames<T> = { [K in keyof T]: T[K] extends TraversableType ? K : never }[keyof T];


//given start object, return 
function indexAny<T extends TraversableType, K extends keyof T>(startObj: T, key: K): T[K] {
  return startObj[key]; 
}

//same thing, but with only "traversable" keys allow
function indexTraverseOnly<T extends TraversableType, K extends TraversablePropNames<T>>(startObj: T, key: K): T[K] {
  return startObj[key]; 
}

let t2: TypeTwo;

type keyType = keyof TypeTwo;                  // "typeTwoNotTraversable" | "typeOne" | "typeThree" | "name"
type keyType2 = TraversablePropNames<TypeTwo>; // "typeOne" | "typeThree"

let r1 = indexAny(t2, 'typeOne');              // TypeOne
let r2 = indexTraverseOnly(t2, 'typeOne');     // TypeOne | TypeThree

Notice how when using K extends keyof T the indexAny function is able to infer the correct return type. 请注意,当使用K extends keyof TindexAny时, indexAny函数能够推断出正确的返回类型。

However, when I try to use the TraversablePropNames conditional mapped type to defined the key, it doesn't know if it's TypeOne or TypeTwo . 但是,当我尝试使用TraversablePropNames条件映射类型定义键时,它不知道它是TypeOne还是TypeTwo

Is there some way to write the function so that it will ONLY allow keys of TraversableType AND will infer the type correctly? 有什么方法可以编写该函数,使其仅允许TraversableType键并正确推断该类型?

UPDATE: 更新:

Interestingly... it seems to work 1 property deep IF I wrap the method in a generic class and pass the instance in (instead of as the first param). 有趣的是...如果我将方法包装在泛型类中并传递实例(而不是作为第一个参数),则似乎可以深入1个属性。 However, it only seem to work for one traversal... then it fails again: 但是,它似乎只适用于一次遍历...然后再次失败:

class xyz<T>{
  private traversable: T;
  constructor(traversable: T) {
    this.traversable = traversable;
  }

   indexTraverseOnly<K extends TraversablePropNames<T>>(key: K): T[K] {
    return this.traversable[key]; 
  }

  indexTraverseTwice<K extends TraversablePropNames<T>, K2 extends TraversablePropNames<T[K]>>(key: K, key2: K2): T[K][K2] {
    return this.traversable[key][key2]; 
  }
}

let t2: TypeTwo;
let r3Obj = new xyz(t2);
let r3 = r3Obj.indexTraverseOnly('typeOne'); // TypeOne (WORKS!)

let r4 = r3Obj.indexTraverseTwice('typeOne', 'typeThree'); // TypeTwo | TypeThree

Because T appears in two positions for the function call (both standalone and in K ) there are basically two positions that can determine the type of T . 因为T出现在函数调用的两个位置(独立和K位置),所以基本上有两个位置可以确定T的类型。 Now usually typescript can handle such cases for simple situations, but using the mapped type will cause it to give up on inferring the type of K . 现在,通常,打字稿可以在简单情况下处理此类情况,但是使用映射的类型会使它放弃推断K的类型。

There are several possible solutions, one of which you discovered, that is to fix T first. 有几种可能的解决方案,您已发现其中一种,即首先修复T You did it with a class, you could also do it with a function that returns a function: 您可以使用一个类来完成它,也可以使用一个返回一个函数的函数来完成它:

function indexTraverseOnly2<T extends TraversableType>(startObj: T) {
  return function <K extends TraversablePropNames<T>>(key: K): T[K] {
    return startObj[key];
  }
}

let r3 = indexTraverseOnly2(t2)('typeThree');     // TypeThree

The other solution would be to specify the constraint that K must a key in T that has a value of TraversableType in a different way, you could say that T must extend Record<K, TraversableType> meaning that key K must have the type TraversableType regardless of any other properties. 另一种解决方案是指定约束,即K必须以不同的方式在T中的键具有TraversableType的值,您可以说T必须扩展Record<K, TraversableType>这意味着键K必须具有TraversableType类型,而无论任何其他属性。

function indexTraverseOnly<T extends Record<K, TraversableType>, K extends keyof any>(startObj: T, key: K): T[K] {
  return startObj[key]; 
}

Edit 编辑

To traverse multiple types you will need to defined multiple overloads. 要遍历多个类型,您将需要定义多个重载。 There is unfortunately no way to do this in a single overload since the parameters are interdependent. 不幸的是,由于参数是相互依赖的,因此无法在单个重载中执行此操作。 You can define up to a reasonable number of overloads: 您最多可以定义合理数量的重载:

function indexTraverseOnly<T extends Record<K, TraversableType & Record<K2,TraversableType& Record<K3,TraversableType>>>, K extends keyof any, K2 extends keyof any, K3 extends keyof any>(startObj: T, key: K, key2:K2, key3:K3): T[K][K2][K3]
function indexTraverseOnly<T extends Record<K, TraversableType & Record<K2,TraversableType>>, K extends keyof any, K2 extends keyof any>(startObj: T, key: K, key2:K2): T[K][K2] 
function indexTraverseOnly<T extends Record<K, TraversableType>, K extends keyof any>(startObj: T, key: K): T[K]
function indexTraverseOnly(startObj: any, ...key: string[]): any {
  return null; 
}

let t2: TypeTwo;

let r1 = indexTraverseOnly(t2, 'typeOne');     // TypeOne
let r2 = indexTraverseOnly(t2, 'typeOne', 'typeTwo'); // TypeTwo
let r3 = indexTraverseOnly(t2, 'typeOne', 'typeTwo', 'typeThree'); // TypeThree

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM