升级到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打字原稿,错误是在调用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]
是一个字符串。但这似乎还不太正确。
您可以通过更改keyObj.toString()
call来轻松解决此问题,无论您通过什么String(keyObj)
调用.toString()
都将在内部调用它,因此行为将保持不变,只是不会在undefined
and上爆炸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] 删除。
我来说两句