【问题标题】:Typescript: generic type restriction on return value of keyofTypescript:对keyof返回值的泛型类型限制
【发布时间】:2019-06-12 08:58:03
【问题描述】:

升级到 TypeScript 3.5 导致我的一些代码不再编译。我认为这是因为一个重大变化:Generic type parameters are implicitly constrained to unknown。我正在尝试找到修复代码的最佳方法。

TL;DR:如何声明类型为 TK extends keyof T 的泛型函​​数,但其​​中 T[K] 必须是字符串。

加长版:我想将一个对象数组转换为一个对象,该对象具有数组中的所有值,并以对象的某些属性为键。例如:

type Person = { id: string, firstName: string, lastName: string, age: number}

const array: Person[] = [
    {id: "a", firstName: "John", lastName: "Smith", age: 27},
    {id: "b", firstName: "Bill", lastName: "Brown", age: 53}
]

const obj = convertArrayToObject(array, "id")

这样做的结果是obj 将具有以下结构:

{ 
    a: {id: "a", firstName: "John", lastName: "Smith", age: 27},
    b: {id: "b", firstName: "Bill", lastName: "Brown", age: 53}
}

我有这个功能来做到这一点:

type ItemsMap<T> = { [key: string]: T }

function convertArrayToObject<T>(array: Array<T>, indexKey: keyof T): ItemsMap<T> {
    return array.reduce((accumulator: ItemsMap<T>, current: T) => {
        const keyObj = current[indexKey]
        const key = (typeof keyObj === "string") ? keyObj : keyObj.toString()
        accumulator[key] = current
        return accumulator
    }, {})
}

自从升级到 TypeScript 3.5 后,错误出现在调用 toString:Property toString does not exist on type T[keyof T]

我可以理解这个问题:由于 TypeScript 3.5 中的重大更改,current[indexKey] 的返回值现在是unknown 而不是一个对象,所以toString 不能被调用。但是我该如何解决呢?

理想情况下,我想做的是对indexKey 参数的类型设置一个通用约束,这样您只能传入一个返回值本身就是字符串的键。以下是我到目前为止所取得的成果(虽然我不确定这是否是最好的方法):

首先,我声明一个类型,用于查找给定类型TObj 的所有属性,它返回给定类型的结果TResult

type PropertiesOfType<TObj, TResult> =
    { [K in keyof TObj]: TObj[K] extends TResult ? K : never }[keyof TObj]

例如,我现在可以获得Person 的所有字符串属性:

type PersonStringProps = PropertiesOfType<Person, string> // "firstName" | "lastName" | "id"

现在我可以将函数声明如下:

function convertArrayToObject<T, K extends PropertiesOfType<T, string>>(
    array: Array<T>, indexKey: K): ItemsMap<T> { ...

我现在只能调用带有返回字符串的属性的函数,例如:

convertArrayToObject(array, "id") // Compiles, which is correct
convertArrayToObject(array, "age") // Doesn't compile, which is correct

但是,在函数体中,我似乎仍然无法使用传入的keyof T 并让编译器知道返回的值是一个字符串:

return array.reduce((accumulator: ItemsMap<T>, current: T) => {
    const key: string = current[indexKey]

这不能编译:Type T[K] is not assignable to type string。我可以通过投射来解决这个问题:

const key: string = current[indexKey] as unknown as string

我想这是安全的,因为我知道current[IndexKey] 是一个字符串。但这似乎仍然不太正确。

【问题讨论】:

    标签: typescript


    【解决方案1】:

    您可以通过将keyObj.toString() 调用更改为String(keyObj) 轻松解决此问题,这将在内部调用.toString(),无论您传递什么,因此行为将保持不变,只是它不会在undefined 和@ 上爆炸987654325@。事实上你可以替换整行:

    const key = (typeof keyObj === "string") ? keyObj : keyObj.toString()
    

    const key = String(keyObj)
    

    因为如果它是一个字符串,它就不会做任何事情。

    更新:

    您几乎有了正确的类型安全解决方案,您只需要对T 进行额外约束:

    function convertArrayToObject<
      T extends { [Key in K]: string }, // This is required for the reduce to work
      K extends PropertiesOfType<T, string>
    >(array: Array<T>, indexKey: K): ItemsMap<T> {
      return array.reduce((accumulator: ItemsMap<T>, current: T) => {
        accumulator[current[indexKey]] = current
        return accumulator
      }, {})
    }
    

    【讨论】:

    • 你是对的,谢谢。这将回到升级到 TS 3.5 之前的行为。理想情况下,虽然我想限制该函数,以便您只能调用它并传入一个 keyof T ,其返回值是一个字符串。我会暂时保留这个问题,看看其他人是否有任何想法。
    • @YoniGibbs 我也用类型安全的解决方案更新了我的答案
    • 啊,完美,非常感谢。对T 的额外限制确实是缺少的。有了这些,就不再需要演员表了。太好了!
    猜你喜欢
    • 2021-02-05
    • 1970-01-01
    • 1970-01-01
    • 2021-03-24
    • 1970-01-01
    • 2017-06-02
    • 2017-12-28
    • 2021-10-06
    • 2019-09-06
    相关资源
    最近更新 更多