打字稿:使用条件映射键时获取正确的推理类型
Typescript: Getting correct inference type when using conditional mapped keys
我正在尝试使用 Conditional Mapped types 来仅允许特定类型的对象键作为函数中的参数。
但是,我 运行 遇到了一个问题,因为当我这样做时没有推断出正确的类型。
我创建了一个示例来演示 (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
注意在使用 K extends keyof T
时,indexAny
函数如何能够推断出正确的 return 类型。
但是,当我尝试使用 TraversablePropNames
条件映射类型来定义键时,它不知道它是 TypeOne
还是 TypeTwo
。
有没有什么方法可以编写函数,使其只允许 TraversableType
的键并正确推断类型?
更新:
有趣的是...如果我将方法包装在通用 class 中并将实例传入(而不是作为第一个参数),它似乎工作 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
的类型。现在通常 typescript 可以处理简单情况下的这种情况,但是使用映射类型会导致它放弃推断 K
的类型。
有几种可能的解决方案,其中一种是您发现的,那就是先修复 T
。你用 class 做的,你也可以用 returns 一个函数做的:
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
我正在尝试使用 Conditional Mapped types 来仅允许特定类型的对象键作为函数中的参数。
但是,我 运行 遇到了一个问题,因为当我这样做时没有推断出正确的类型。
我创建了一个示例来演示 (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
注意在使用 K extends keyof T
时,indexAny
函数如何能够推断出正确的 return 类型。
但是,当我尝试使用 TraversablePropNames
条件映射类型来定义键时,它不知道它是 TypeOne
还是 TypeTwo
。
有没有什么方法可以编写函数,使其只允许 TraversableType
的键并正确推断类型?
更新:
有趣的是...如果我将方法包装在通用 class 中并将实例传入(而不是作为第一个参数),它似乎工作 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
的类型。现在通常 typescript 可以处理简单情况下的这种情况,但是使用映射类型会导致它放弃推断 K
的类型。
有几种可能的解决方案,其中一种是您发现的,那就是先修复 T
。你用 class 做的,你也可以用 returns 一个函数做的:
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