升级到TypeScript 3.5导致我的某些代码不再编译。我认为这是因为发生了重大变化:Generic type parameters are implicitly constrained to 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
}, {})
}
自从升级到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]
无法编译:{{1}}。我可以通过以下方法解决这个问题:
Type T[K] is not assignable to type string
我猜这很安全,因为我知道const key: string = current[indexKey] as unknown as string
是一个字符串。但这似乎还不太正确。
答案 0 :(得分:2)
您可以通过将keyObj.toString()
调用更改为String(keyObj)
来轻松解决此问题,无论您通过什么调用,它都会在内部调用.toString()
,因此行为不会改变,只是不会爆炸在undefined
和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
}, {})
}