打字稿:keyof返回值的通用类型限制

约尼·吉布斯(Yoni Gibbs)

升级到TypeScript 3.5已导致我的某些代码不再编译。我认为这是因为发生了重大变化:通用类型参数隐式地限制为unknown我正在尝试找到最好的方法来立即修复代码。

TL; DR:如何声明类型为T的泛型函数K 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
    }, {})
}

由于升级到3.5打字原稿,错误是在调用toStringProperty 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]是一个字符串。但这似乎还不太正确。

德米特里

您可以通过更改keyObj.toString()call来轻松解决此问题,无论您通过什么String(keyObj)调用.toString()将在内部调用它,因此行为将保持不变,只是不会在undefinedand上爆炸null实际上,您可以替换整行:

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
  }, {})
}

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章