如何交换对象键值并获得交换函数的有效返回类型?

Art*_*rev 3 typescript typescript-generics

当我交换对象键值时,函数的返回类型是 { [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',
});
Run Code Online (Sandbox Code Playgroud)

操场

jca*_*alz 9

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

{ [K in keyof T as T[K]]: K }
Run Code Online (Sandbox Code Playgroud)

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


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

swapFn({ s: "t", u: "v" } as const);
Run Code Online (Sandbox Code Playgroud)

但如果您不希望呼叫者必须这样做,您可以使用其他技巧。请参阅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 };
}
Run Code Online (Sandbox Code Playgroud)

我改变了类型断言;编译器并没有真正具备验证“交换键和值”实现的类型安全性的能力,因此我懒得尝试,并使用any.

让我们看看它是否有效:

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

看起来不错!

Playground 代码链接