typescript - 用于泛型函数回调的 TypeScript 类型缩小
问题描述
我正在尝试实现此处概述的多维数组排序功能: https ://stackoverflow.com/a/55620991/2175753
它并不过分复杂,但是函数的类型设置让我头疼 atm。
这是我到目前为止所拥有的:
export function orderBy<T, U extends keyof T>(array: Array<T>, keys: Array<U>, sort: (key: U, a: T[U], b: T[U]) => number) {
const records = keys.map(key => {
const record = {
key,
uniques: Array.from(new Set(array.map(item => item[key])))
}
record.uniques = record.uniques.sort((a, b) => sort(key, a, b))
return record
})
const weightOfObject = (obj: T) => {
let weight = ''
records.map(record => {
let zeropad = `${record.uniques.length}`.length
weight += record.uniques.indexOf(obj[record.key]).toString().padStart(zeropad, '0')
})
return weight
}
return array.sort((a, b) => weightOfObject(a).localeCompare(weightOfObject(b)))
}
在呼叫站点上,它看起来像这样:
问题是,我无法进一步缩小 a 和 b 的类型。我希望这会起作用:
我试图调整函数签名以明确表示回调是通用的,如下所示:
export function orderBy<T, U extends keyof T>(array: Array<T>, keys: Array<U>, sort: <V extends U>(key: V, a: T[V], b: T[V]) => number) { ... }
但无济于事。
这是 TypeScript 不支持的东西还是我做错了什么?
解决方案
问题是打字稿只跟踪值类型之间的连接,只要它们在同一个变量中,比如一个对象的两个属性或一个元组的两个元素。一旦您将这些值分成不同的变量,例如通过解构它们或仅在函数中作为 2 个不同的参数传递,就像您的情况一样,所有连接都会丢失并且无法恢复它们(至少现在,也许有一天它会成为可能)。考虑一个可能经常出现的情况
type Foo =
| { type: 'a', value: number }
| { type: 'b', value: string }
declare const obj: Foo
// Here there is connection between obj.type and obj.value
switch(obj.type) {
case 'a':
// Here obj.value is of type number
break
case 'b':
// Here obj.value is of type string
break
}
但如果你这样做
const {type, value} = obj
// At this point type and value are distinct variables
// and any connection they had is lost
switch(type) {
case 'a':
// Here value is number | string
break
case 'b':
// And here as well
break
}
而且您对此无能为力,您必须将值保留在同一个对象中,以便打字稿保留有关它们的类型如何相互依赖的任何有用信息。
所以这正是你在你的场景中可以做的:将这些值放在一个对象中,不要解构它。这是一种方法。第一次尝试可能如下所示:
declare function orderBy<T, U extends keyof T>(
array: T[],
keys: U[],
sort: (data: {key: U, a: T[U], b: T[U]}) => number
): T[]
但它实际上不起作用,因为T[U]
在number | Date
您提供的特定示例中,因此类型之间的依赖关系立即丢失。要保留它,您需要“映射”此联合类型U
,以便此联合的每个成员都映射到具有合适类型的对象。这可以通过这样的辅助类型来完成:
type MapKeysToSortData<T, U extends keyof T> = {
[TKey in U]: {
key: TKey,
a: T[TKey],
b: T[TKey],
}
}[U]
因此,您正在创建一个对象,其中键是 的成员U
,您为每个严格键入的键分配一个对象,然后在最后得到这些对象与 this 的联合[U]
。所以函数看起来像这样:
declare function orderBy<T, U extends keyof T>(
array: T[],
keys: U[],
sort: (data: MapKeysToSortData<T, U>) => number
): T[]
差不多就是这样。在向此声明添加实现时,您可能会遇到一些问题,我不打算在这里输入它,无论如何您可以输入一些类型断言,它会做到的。虽然我很确定你可以在 2021 年在没有类型断言的情况下输入它。
现在使用该函数而不破坏回调中的参数(至少直到打字稿已经推断出您需要的所有内容的那一刻)
orderBy([testObject], ['age', 'eta'], (data) => {
if(data.key === 'eta') {
// Here TS already knows data.a and data.b are of type Date, so you can destructure them
const {a, b} = data
console.log(a.toUTCString(), b.toUTCString())
} else {
// Here data.a and data.b are of type number
}
return 1
})
另外我要注意,您不是在排序多维数组,而只是对对象的一维数组进行排序。并且请不要插入代码的屏幕截图,将实际代码复制为文本,并添加描述 IDE 的这些悬停事物的注释,从图像中复制测试数据有点烦人。
推荐阅读
- javascript - JS DataTable添加导出按钮不显示
- go - 乘以正 int8s 并转换为 uint32 后溢出
- tableau-desktop - 如何在计算中考虑多个度量值
- linux - 从自动字符串 bash 脚本中删除前缀
- c# - 使用带有自动映射器的 CreateMap 时访问列表的属性
- javascript - setstate后如何调度
- python - 我无法在 python 中使用 selenium 在 Chrome 中打开特定的配置文件
- wordpress - 如何将网站 301 永久重定向到另一个网站和永久链接的子类别(使用 .htaccess)
- r - 如何在R中遍历数据框列中的行值
- java - 解析问题-XML java