泛型类型的Typescript访问值,使用受该值类型约束的键



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

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

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?键被适当地约束。我不能将参数传递给函数,导致不是是日期。

onlyTakesADateKey的实现中,对于依赖于尚未指定的泛型参数(如TK)的条件类型,编译器实际上不能做很多事情。在函数实现内部,类型T[K] extends Date ? K : never的求值是延迟的。这就是为什么你看到关于T[T[K] extends Date ? K : never]的错误。编译器无法进行必要的高阶推理,得出它必须可赋值给Date的结论。这是TypeScript的设计限制,从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.

如果你真的不想改变你的调用,你总是可以使用类型断言来告诉编译器它不能弄清楚的: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链接到代码

最新更新