TypeScript:如何创建类型安全的 mapObject 函数?
TypeScript: How to create a type-safe mapObject function?
我创建了一个 util 函数,mapObject
,它是 Array.map
但对于对象。
原来是这么用的...
mapObject(
(value) => value * 100,
{a: 1, b: 2, c: 3}
)
// {a: 100, b: 200, c: 300}
这是实现。我得到了一个 pretty 类型安全版本,但不得不消除关于使用 obj[key]
没有索引签名的 2 个错误。
type ValueOf<T> = T[keyof T];
export const mapObject = <OldObject, NewValue>(
mappingFn: (value: ValueOf<OldObject>) => NewValue,
obj: OldObject
): Record<keyof OldObject, NewValue> =>
Object.keys(obj).reduce((newObj, key) => {
// @ts-ignore (Not sure how to work around this)
const oldValue = obj[key];
// @ts-ignore (Not sure how to work around this)
newObj[key] = mappingFn(oldValue);
return newObj;
}, {} as Record<keyof OldObject, NewValue>);
有什么更好的编码方法?该函数正确地推断出传入的内容,并且 return 值具有很好的类型安全性。我很想删除 @ts-ignore
s,但是 不使用索引签名 因为它们会损害类型安全。
Object.keys
returnstring[]
。一种可能的解决方案是按照 中的建议对 Object.keys
使用类型断言:
(Object.keys(obj) as Array<keyof typeof obj>).reduce((newObj, key) => {
...
})
如果您使用 for 循环而不是 reduce
,则 TypeScript 能够正确跟踪 i
迭代器变量的类型。
否则,正如 Psidom 指出的那样,Object.keys()
returns string[]
会丢失 property:value 映射。
这是您的代码的最小更改版本:
type ValueOf<T> = T[keyof T];
export const mapObject = <OldObject extends object, NewValue>(
mappingFn: (value: ValueOf<OldObject>) => NewValue,
obj: OldObject
): Record<keyof OldObject, NewValue> => {
let newObj = {} as Record<keyof OldObject, NewValue>;
for (let i in obj) {
if (obj.hasOwnProperty(i)) {
const oldValue = obj[i];
newObj[i] = mappingFn(oldValue);
}
}
return newObj;
}
但是有几种不同的注释方法可以达到相同的结果,语义略有不同。
这是使用 mapped types 的替代版本(它看起来很相似,但不是索引签名)。
这可能稍微灵活一些,因为输入对象不需要是具有相同类型值的Record
类型。
实现是一样的,只是类型注解不同:
type ValueOf<T> = T[keyof T];
type MapTo<T, U> = {
[P in keyof T]: U
}
function mapObject<T extends object, U>(mappingFn: (v: ValueOf<T>) => U, obj: T): MapTo<T, U> {
let newObj = {} as MapTo<T, U>;
for (let i in obj) {
if (obj.hasOwnProperty(i)) {
const oldValue = obj[i];
newObj[i] = mappingFn(oldValue);
}
}
return newObj;
}
试试 ramda 的 mapObjIndexed。
export function mapObjIndexed<T, TResult, TKey extends string>(
fn: (value: T, key: TKey, obj?: Record<TKey, T>) => TResult,
obj: Record<TKey, T>
): Record<TKey, TResult>;
我创建了一个 util 函数,mapObject
,它是 Array.map
但对于对象。
原来是这么用的...
mapObject(
(value) => value * 100,
{a: 1, b: 2, c: 3}
)
// {a: 100, b: 200, c: 300}
这是实现。我得到了一个 pretty 类型安全版本,但不得不消除关于使用 obj[key]
没有索引签名的 2 个错误。
type ValueOf<T> = T[keyof T];
export const mapObject = <OldObject, NewValue>(
mappingFn: (value: ValueOf<OldObject>) => NewValue,
obj: OldObject
): Record<keyof OldObject, NewValue> =>
Object.keys(obj).reduce((newObj, key) => {
// @ts-ignore (Not sure how to work around this)
const oldValue = obj[key];
// @ts-ignore (Not sure how to work around this)
newObj[key] = mappingFn(oldValue);
return newObj;
}, {} as Record<keyof OldObject, NewValue>);
有什么更好的编码方法?该函数正确地推断出传入的内容,并且 return 值具有很好的类型安全性。我很想删除 @ts-ignore
s,但是 不使用索引签名 因为它们会损害类型安全。
Object.keys
returnstring[]
。一种可能的解决方案是按照 Object.keys
使用类型断言:
(Object.keys(obj) as Array<keyof typeof obj>).reduce((newObj, key) => {
...
})
如果您使用 for 循环而不是 reduce
,则 TypeScript 能够正确跟踪 i
迭代器变量的类型。
否则,正如 Psidom 指出的那样,Object.keys()
returns string[]
会丢失 property:value 映射。
这是您的代码的最小更改版本:
type ValueOf<T> = T[keyof T];
export const mapObject = <OldObject extends object, NewValue>(
mappingFn: (value: ValueOf<OldObject>) => NewValue,
obj: OldObject
): Record<keyof OldObject, NewValue> => {
let newObj = {} as Record<keyof OldObject, NewValue>;
for (let i in obj) {
if (obj.hasOwnProperty(i)) {
const oldValue = obj[i];
newObj[i] = mappingFn(oldValue);
}
}
return newObj;
}
但是有几种不同的注释方法可以达到相同的结果,语义略有不同。
这是使用 mapped types 的替代版本(它看起来很相似,但不是索引签名)。
这可能稍微灵活一些,因为输入对象不需要是具有相同类型值的Record
类型。
实现是一样的,只是类型注解不同:
type ValueOf<T> = T[keyof T];
type MapTo<T, U> = {
[P in keyof T]: U
}
function mapObject<T extends object, U>(mappingFn: (v: ValueOf<T>) => U, obj: T): MapTo<T, U> {
let newObj = {} as MapTo<T, U>;
for (let i in obj) {
if (obj.hasOwnProperty(i)) {
const oldValue = obj[i];
newObj[i] = mappingFn(oldValue);
}
}
return newObj;
}
试试 ramda 的 mapObjIndexed。
export function mapObjIndexed<T, TResult, TKey extends string>(
fn: (value: T, key: TKey, obj?: Record<TKey, T>) => TResult,
obj: Record<TKey, T>
): Record<TKey, TResult>;