简体   繁体   English

Typescript:通过通用类型的缩小键索引时的对象属性类型

[英]Typescript: object property type when indexed by narrowed keyof generic type

I need to supply an adapter function that takes an object and returns a value of a given type or types. 我需要提供一个接受对象并返回给定类型值的适配器函数。 I am trying to write a generic function that will generate such an adapter function given an object property name, where the property names are restricted to properties whose value is of the given type(s). 我正在尝试编写一个通用函数,该函数将在给定对象属性名称的情况下生成此类适配器函数,其中属性名称仅限于其值是给定类型的属性。

For example, consider the simple case of numeric properties: 例如,考虑数字属性的简单情况:

type Adapter<T> = (from: T) => number

I think I want to do something like: 我想我想做些类似的事情:

type NumberFields<T> = {[K in keyof T]: T[K] extends number ? K : never}[keyof T]

function foo<T>(property: NumberFields<T>) {
    return function(from: T) {
        return from[property]
    }
}

TypeScript is happy enough when the argument to NumberFields is not generic: NumberFields的参数不是通用参数时,TypeScript足够高兴:

interface Foo {
    bar: number
}

function ok<T extends Foo, K extends NumberFields<Foo>>(property: K): Adapter<T> {
    return function(from: T): number {
        return from[property] // ok
    }
}

However, in the generic case TypeScript doesn't want to recognise that from[property] can only possibly be a number: 但是,在一般情况下,TypeScript不想识别from[property]只能是数字:

function bar<T, K extends NumberFields<T>>(property: K): Adapter<T> {
    return function(from: T): number {
        return from[property] // error: Type 'T[K]' is not assignable to to type 'number'
    }
}

function baz<T, K extends NumberFields<T>>(property: K): Adapter<T> {
    return function(from: T): T[K] { // error: Type '(from: T) => T[K]' is not assignable to type 'Adapter<T>'
        return from[property]
    }
}

Why does it work when the type is constrained, but not in the truly generic case? 为什么在类型受约束时才起作用,但在真正通用的情况下却不起作用?

I have looked at many SO questions, of which Narrowing TypeScript index type seemed the closest to what I want, but a type guard isn't useful here since the type should be generic. 我看了很多SO问题,其中Narrowing TypeScript索引类型似乎最接近我想要的类型,但是类型保护在这里没有用,因为类型应该是通用的。

The compiler just isn't clever enough to see through such type manipulation of generic type parameters. 编译器还不够聪明,无法通过泛型类型参数的这种类型操作来查看。 With concrete types, the compiler can just evaluate the final type as a bag of concrete properties, so it knows that the output will be of type Foo['bar'] which is type number . 与具体类型,编译器可以只评价最终类型作为混凝土特性的袋,因此它知道的输出将是类型的Foo['bar']这是类型number

There are different ways to proceed here. 这里有不同的处理方法。 Often the most convenient is to use a type assertion because you are more clever than the compiler. 通常最方便的是使用类型断言,因为您比编译器更聪明。 This keeps the emitted JavaScript the same, but just tells the compiler not to worry: 这使发出的JavaScript保持相同,但是只是告诉编译器不要担心:

function bar<T, K extends NumberFields<T>>(property: K): Adapter<T> {
  return function (from: T): number {
    return from[property] as unknown as number; // I'm smarter than the compiler 🤓
  }
}

Of course it's not type-safe, so you need to be careful not to lie to the compiler (eg, from[property] as unknown as 12345 will also compiler but is probably not true). 当然,它不是类型安全的,因此您需要注意不要对编译器撒谎(例如, from[property] as unknown as 12345也会编译,但可能不正确)。

Equivalent to type assertions is the single- overload function, where you give a call signature that is as precise as you want, and then loosen it up so the implementation doesn't complain: 与类型断言等效的是单重载函数,在此函数中,您可以根据需要提供精确的调用签名,然后松开它,以使实现不会抱怨:

// callers see this beautiful thing
function bar<T, K extends NumberFields<T>>(property: K): Adapter<T>;

// implementer sees this ugly thing
function bar(property: keyof any): Adapter<any>
{
  return function (from: any): number {
    return from[property]; 
  }
}

The other way to go is try to let the compiler assure you of type safety, by retyping the function into a form that the compiler is able to understand. 另一种方法是尝试通过将函数重新键入为编译器可以理解的形式,以使编译器向您保证类型安全。 Here's a less deeply-nested way to do it: 这是一种不太嵌套的方法:

function bar<T extends Record<K, number>, K extends keyof any>(property: K): Adapter<T> {
  {
    return function (from: T): number {
      return from[property] 
    }
  }
}

In this case we're representing T in terms of K , not the other way around. 在这种情况下,我们用K表示T ,而不是相反。 And no errors are here. 而且这里没有错误。 The only reason not to go with this method is that maybe you want errors to appear on K and not on T . 不使用此方法的唯一原因是,也许您希望错误出现在K而不是T That is, when people call bar() with a bad parameter, you'd like it to complain that their K type is wrong, not that their T type is wrong. 也就是说,当人们使用错误的参数调用bar()时,您希望抱怨他们的K类型是错误的,而不是他们的T类型是错误的。

You can sort of get the best of both worlds by doing this: 通过执行以下操作,您可以兼得两全其美:

function bar<T, K extends NumberFields<T>>(property: K): Adapter<T>;
function bar<T extends Record<K, number>, K extends keyof any>(property: K): Adapter<T> {
  {
    return function (from: T): number {
      return from[property]
    }
  }
}

One last thing... I'm not sure how you're planning to call bar() . 最后一件事...我不确定您打算如何调用bar() Do you mean to specify the type parameters yourself? 您是要自己指定类型参数吗?

bar<Foo, "bar">("bar");

Or do you want the compiler to infer them? 还是您希望编译器来推断它们?

bar("bar")?

If it's the latter, you're probably out of luck, since there's nowhere to infer T except from possibly some external context. 如果是后者,则可能很不走运,因为除了可能的某些外部环境之外,没有其他地方可以推断出T So you might want to look into this more. 因此,您可能想对此进行更多研究。


Okay, hope that helps. 好的,希望对您有所帮助。 Good luck! 祝好运!

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

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