如何扩展keyof类型以使其包含键的修改版本,例如以“-”为前缀?
例如,我想将 Typescript 类型安全添加到 vanilla Javascript Sort array of objects by string property value解决方案。它接受要排序的对象的 args 键,以确定要排序的键。如果键以 为前缀-,则排序相反。
我将如何输入 arg 以接受,例如,两者"age"和"-age"?
这是我的尝试:
export function dynamicSortMultiple<T extends object, U extends keyof T>(
props: Array<U>,
) {
function dynamicSort(key: U) {
let sortOrder = 1
if (typeof key === 'string' && key.startsWith('-')) {
sortOrder = -1
}
return function (a: T, b: T) {
const result = a[key] < b[key] ? -1 : a[key] > b[key] ? 1 : 0
return result * sortOrder
}
}
return function (obj1: T, obj2: T) {
let i = 0
let result = 0
const numberOfProperties = props?.length
while (result === 0 && i < numberOfProperties) {
result = dynamicSort(props[i])(obj1, obj2)
i++
}
return result
}
}
export interface User {
userId: string
firstName: string
lastName: string
login: string
password: string
rank: number
score: number
age?: number
}
dynamicSortMultiple<User, keyof User>(['firstName', 'score', '-age'])
打字稿游乐场
在最后一行我看到错误
Type '"-age"' is not assignable to type 'keyof User'.
有什么方法可以keyof User正确扩展,以便以“-”为前缀的值仍然被视为该类型的有效值?
即使您完全更换了我的解决方案,我也将不胜感激。
回答
我的变化:
-
T 和 U 通用参数是多余的。只需要T。
注意:我最初只是用 替换了您的所有
U引用
keyof T,但后来将其拉出来sortArg以方便 #2。 -
使用TS 4.1 中引入的模板文字类型
-
您忘记
-在 startsWith('-') 情况下修剪前缀 -
在 Typescript 无法将类型缩小到必须为其提供逻辑流的情况下使用类型断言(TS 团队一直在改进 TS 编译器的流分析,所以我打赌有一天这将是自动的)
-
API 改进:重命名该函数并添加了一个方便的
sort函数,该函数在代码中可读性更好(请参阅解决方案代码后面的
示例用法)。
type sortArg<T> = keyof T | `-${string & keyof T}`
/**
* Returns a comparator for objects of type T that can be used by sort
* functions, were T objects are compared by the specified T properties.
*
* @param sortBy - the names of the properties to sort by, in precedence order.
* Prefix any name with `-` to sort it in descending order.
*/
export function byPropertiesOf<T extends object> (sortBy: Array<sortArg<T>>) {
function compareByProperty (arg: sortArg<T>) {
let key: keyof T
let sortOrder = 1
if (typeof arg === 'string' && arg.startsWith('-')) {
sortOrder = -1
// Typescript is not yet smart enough to infer that substring is keyof T
key = arg.substr(1) as keyof T
} else {
// Likewise it is not yet smart enough to infer that arg is not keyof T
key = arg as keyof T
}
return function (a: T, b: T) {
const result = a[key] < b[key] ? -1 : a[key] > b[key] ? 1 : 0
return result * sortOrder
}
}
return function (obj1: T, obj2: T) {
let i = 0
let result = 0
const numberOfProperties = sortBy?.length
while (result === 0 && i < numberOfProperties) {
result = compareByProperty(sortBy[i])(obj1, obj2)
i++
}
return result
}
}
/**
* Sorts an array of T by the specified properties of T.
*
* @param arr - the array to be sorted, all of the same type T
* @param sortBy - the names of the properties to sort by, in precedence order.
* Prefix any name with `-` to sort it in descending order.
*/
export function sort<T extends object> (arr: T[], ...sortBy: Array<sortArg<T>>) {
arr.sort(byPropertiesOf<T>(sortBy))
}
用法示例:
interface User {
name: string
id: string
age?: number
}
const users: User[] = [
{name: 'Harriet Tubman', id: '01', age: 53},
{name: 'John Brown', id: '02', age: 31},
{name: 'John Brown', id: '03', age: 59},
{name: 'James Baldwin', id: '04', age: 42},
{name: 'Greta Thunberg', id: '05', age: 17}
]
// using Array.sort directly
users.sort(byPropertiesOf<User>(['name', '-age', 'id']))
// using the convenience function for much more readable code
sort(users, 'name', '-age', 'id')