首页 > 解决方案 > 如何交换对象键值并获得交换函数的有效返回类型?

问题描述

当我交换对象键值时,函数的返回类型是 { [x: string]: value } instedof const key。

这里的游乐场

interface IObj {
    [key: string]: string;
};

const swapFn = <T extends IObj>(obj: T) => {
    const res = {} as { [K in T[keyof T]]: keyof T };

    Object.entries(obj).forEach(([key, value]) => {
        res[value as T[keyof T]] = key;
    });
    return res;
}

// return type should be:
// {
//     aaa: 'a',
//     bbb: 'b',
// } insted of {
//     [x: string]: "a" | "b";
// }
const result = swapFn({
    a: 'aaa',
    b: 'bbb',
});

操场

标签: typescripttypescript-generics

解决方案


这里有两个问题。首先是您的返回类型太宽,因为keyof T是所有键的联合;每个属性都将是"a" | "b"而不是特定的"a""b"对应于该值。这可以通过更改映射类型来解决,并且可以通过TypeScript 4.1 中引入的键重新映射特别容易地完成:

{ [K in keyof T as T[K]]: K }

我们遍历 的每个键KT并将输出T[K](属性值类型)作为键,并K作为属性。这交换了东西。


第二个问题是推理问题。当您传入编译器时{ a: 'aaa', b: 'bbb'},编译器很可能会将该值推断为具有类型{a: string, b: string},因为人们可能希望将属性的值更改为其他值string而不是将其保留为某个字符串文字更常见。有不同的方法可以说服编译器推断出更窄的类型。最简单的是调用者使用const断言

swapFn({ s: "t", u: "v" } as const);

但是,如果您不希望调用者必须这样做,您可以使用其他技巧。请参阅microsoft/TypeScript#30680以获取功能请求,要求提供一些不那么疯狂的技巧和对它们的一些描述。我将添加另一个泛型类型参数S extends string,除了向编译器提示您需要字符串文字类型之外,它没有其他用途。代替T extends IObj,我会写T extends Record<string, S>


这是完整的功能:

const swapFn = <T extends Record<string, S>, S extends string>(obj: T) => {
    const res = {} as any; // I'm not worried about impl safety
    Object.entries(obj).forEach(([key, value]) => {
        res[value] = key;
    });
    return res as { [K in keyof T as T[K]]: K };
}

我改变了类型断言;编译器并没有真正具备验证“交换键和值”实现的类型安全性,所以我不想费心去尝试,而是使用any.

让我们看看它是否有效:

const result = swapFn({
    a: 'aaa',
    b: 'bbb',
});
/* const result: {
    aaa: "a";
    bbb: "b";
} */
console.log(result.aaa.toUpperCase()) // A

看起来挺好的!

Playground 代码链接


推荐阅读