如何扩展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正确扩展,以便以“-”为前缀的值仍然被视为该类型的有效值?

即使您完全更换了我的解决方案,我也将不胜感激。

回答

我的变化:

  1. T 和 U 通用参数是多余的。只需要T。

    注意:我最初只是用 替换了您的所有U引用
    keyof T,但后来将其拉出来sortArg以方便 #2。

  2. 使用TS 4.1 中引入的模板文字类型

  3. 您忘记-在 startsWith('-') 情况下修剪前缀

  4. 在 Typescript 无法将类型缩小到必须为其提供逻辑流的情况下使用类型断言(TS 团队一直在改进 TS 编译器的流分析,所以我打赌有一天这将是自动的)

  5. 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')


以上是如何扩展keyof类型以使其包含键的修改版本,例如以“-”为前缀?的全部内容。
THE END
分享
二维码
< <上一篇
下一篇>>