我想要一个泛型,我可以在保留输入的同时按名称索引。能够这样写会很有用:
candidates.sort(compareByClosestTo("recieved", date))
我觉得很接近,但似乎无法完成这个
// generic comparison fn with index
type Dateable<T> = {
[P in keyof T]: DateTime
};
const compareByClosestTo = <T extends Dateable<T>>(key: keyof T, date: DateTime) =>
(l: T, r: T) => Math.abs(l[key].diff(date).milliseconds) - Math.abs(r[key].diff(date).milliseconds)
然而,这会产生错误,因为它要求所有参数都为 DateTime 类型。有什么办法可以简单地限制T[key]的类型来输入?
编辑
发布此内容 5 分钟后,我遇到了 a hint here,这导致我:
function sortByClosest<K extends string>(data: Record<K, DateTime>[], key: K, date: DateTime): void {
data.sort((l, r) =>
Math.abs(l[key].diff(date).milliseconds) - Math.abs(r[key].diff(date).milliseconds))
}
但是,我想保持开放,因为我真的很想知道如何在回调工厂中找出类型化的通用索引(即,是否可以使用顶级形式?)
答案 0 :(得分:1)
我认为您的原始 compareByClosestTo()
函数中的主要问题是 T
没有好的推理站点。 key
参数的类型为 keyof T
。如果您传入 "foo"
为 key
,则需要编译器推断类型 T
,其中 "foo"
是其键之一。编译器可能推断 {foo: DateTime}
,这取决于您如何使用它,或者它可能会放弃并推断出类似 {foo: any}
甚至 {[k: string]: any}
的东西。您可以期望编译器从 "foo"
类型的值可靠地推断出的唯一内容是字符串 "foo"
本身。
为此,让我们不用 T extends Dateable<T>
,而是看看 K extends PropertyKey
。然后我们可以用 T
来表达 K
:在这种情况下,{ [P in K]: DateTime }
等价于 Record<K, DateTime>
(使用 the Record
utility type):
const compareByClosestTo = <K extends PropertyKey>(key: K, date: DateTime) =>
(l: { [P in K]: DateTime }, r: { [P in K]: DateTime }) =>
Math.abs(l[key].diff(date).milliseconds) - Math.abs(r[key].diff(date).milliseconds);
让我们看看调用它时会发生什么:
const compareFn = compareByClosestTo("recieved", DateTime.local());
/* const compareFn: (l: {
recieved: DateTime;
}, r: {
recieved: DateTime;
}) => number */
这里,K
被推断为 "received"
,结果比较函数就是我们想要的 {received: DateTime}
类型。因此以下两个 sort()
调用都有效:
const works = dates.map(dt => ({ recieved: dt }))
.sort(compareByClosestTo("recieved", DateTime.local()))
const stillWorks = dates.map((dt, i) => ({ recieved: dt, index: i }))
.sort(compareByClosestTo("recieved", DateTime.local()))
因为您排序的每个数组都有可分配给 {received: DateTime}
的元素。