本文介绍了打字稿:使用条件映射键时获取正确的推断类型的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试使用条件映射类型仅获取特定类型的对象的键作为函数中的参数.

但是,我遇到了一个问题,当我这样做时,不能正确地推断出正确的类型.

我创建了一个示例来演示(查看打字稿游乐场):

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

请注意,在使用K extends keyof T时,indexAny函数如何能够推断出正确的返回类型.

但是,当我尝试使用TraversablePropNames条件映射类型定义键时,它不知道它是TypeOne还是TypeTwo.

是否可以通过某种方式编写该函数,使其仅允许TraversableType键并正确推断类型?

更新:

有趣的是,如果我将方法包装在泛型类中并传递实例(而不是作为第一个参数),则似乎可以深入1个属性.但是,它似乎只适用于一次遍历...然后再次失败:

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
解决方案

由于T出现在函数调用的两个位置(独立调用和K中),因此基本上有两个位置可以确定T.现在,通常,打字稿可以在简单情况下处理此类情况,但是使用映射的类型会使它放弃推断K的类型.

有几种可能的解决方案,您已找到其中一种,即首先修复T.您可以使用一个类来完成它,也可以使用一个返回一个函数的函数来完成它:

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

另一种解决方案是指定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];
}

修改

要遍历多种类型,您将需要定义多个重载.不幸的是,由于参数是相互依赖的,因此无法在单个重载中执行此操作.您最多可以定义合理数量的重载:

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

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.

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.

Is there some way to write the function so that it will ONLY allow keys of TraversableType AND will infer the type correctly?

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). 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. 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.

There are several possible solutions, one of which you discovered, that is to fix T first. 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.

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

这篇关于打字稿:使用条件映射键时获取正确的推断类型的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

08-18 13:09