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-ignores,但是 不使用索引签名 因为它们会损害类型安全。

Object.keysreturnstring[]。一种可能的解决方案是按照 中的建议对 Object.keys 使用类型断言:

(Object.keys(obj) as Array<keyof typeof obj>).reduce((newObj, key) => {
...
})

Typescript playground.

如果您使用 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>;