泛型类型的TypeScrip访问值使用由该值的类型约束的键类型、由该、TypeScrip

2023-09-03 13:32:19 作者:你是我年少时最冒险的梦

我有一个类型脚本函数,它接受泛型类型和该泛型类型的键。我对该键进行了约束,以便该函数将只接受值为特定类型的键。使用约束键访问泛型对象时,返回的类型不是预期的类型。

如何将泛型对象的键约束为特定值类型并在泛型函数中访问该值?

例如:

function onlyTakesADateKey<T, K extends keyof T>(item: T, key: T[K] extends Date ? K : never): void {
    //I've constrained `key` to ensure that item[key] is a Date but this doesn't get typed as a Date
    const shouldBeADateButIsNot = item[key]
    //Property 'getTime' does not exist on type 'T[T[K] extends Date ? K : never]'.ts(2339)
    shouldBeADateButIsNot.getTime()
}
const foo = { key1: "asdf", key2: new Date() }
//Argument of type 'string' is not assignable to parameter of type 'never'.ts(2345)
const bar = onlyTakesADateKey(foo, "key1")
//It properly constrains the key, but per above can't access the date value in the function
const baz = onlyTakesADateKey(foo, "key2")
为什么shouldBeADateButIsNot不是Date?该关键点受到适当的约束。我无法将参数传递给导致不是为日期的函数。

推荐答案

ZUI Brim 使用指南

编译器实际上无法处理依赖于onlyTakesADateKey实现中尚未指定的TK等类型参数的conditional types。在函数实现内部,T[K] extends Date ? K : never类型的计算是延迟的。这就是为什么您会看到关于T[T[K] extends Date ? K : never]的错误。编译器无法进行必要的高阶推理,从而得出结论:它必须可赋值给Date。这是打字稿的设计限制,可以在microsoft/TypeScript#30728中看到。

编译器通常会推迟对依赖于未指定泛型的类型的求值,但也有一些地方可以做得更好。一种是:如果您有一个Record<K, V>类型的值,并用K对其进行索引,编译器将理解它是V类型。因此,泛型查找并不总是完全延迟的。这建议重写TK约束,如下所示:

function onlyTakesADateKey<T extends Record<K, Date>, K extends PropertyKey>(
  item: T, key: K): void {
    const isActuallyADateNow = item[key]
    isActuallyADateNow.getTime()
}

这是没有错误的,并且您的示例行为类似:

const foo = { key1: "asdf", key2: new Date() }
const baz = onlyTakesADateKey(foo, "key2"); // okay

有一个明显的例外,即当您出错时,编译器会抱怨item而不是key

const bar = onlyTakesADateKey(foo, "key1"); // error!
// -------------------------> ~~~
// Types of property 'key1' are incompatible.

如果您真的不想更改调用的任何内容,您总是可以使用type assertion只告诉编译器它不能弄明白的:shouldBeADateButIsNotDate

function onlyTakesADateKeyOrig<T, K extends keyof T>(
  item: T, key: T[K] extends Date ? K : never): void {
    const shouldBeADateButIsNot = item[key] as any as Date;
    shouldBeADateButIsNot.getTime()
}

Playground link to code