【问题标题】:How can I swap object key value and get valid return type of swap function?如何交换对象键值并获得交换函数的有效返回类型?
【发布时间】:2021-04-20 14:46:23
【问题描述】:

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

游乐场here

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',
});

playground

【问题讨论】:

标签: typescript typescript-generics


【解决方案1】:

这里有两个问题。首先是你的返回类型太宽,因为keyof T 是所有键的联合;每个属性将是"a" | "b",而不是与该值对应的特定"a""b"。这可以通过更改映射类型来解决,并且可以通过 TypeScript 4.1 中介绍的key remapping 轻松完成:

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

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


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

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

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


这是完整的功能:

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 link to code

【讨论】:

  • 如果对象值是字符串的另一个对象怎么办? const result = swapFn({ a: { val: 'aaa' }, b: { val: 'bbb' }, }); 是否可以正确输入,你怎么看?我编辑了操场
  • 当然,比如this。尽管这似乎偏离了所问问题的范围。
【解决方案2】:

另外一种方式,可以严格固定key/val的类型。

export const swapFn = <
  T extends string | number,
  S extends string | number
>(
  obj: Record<T, S>
) => {
  const res = {} as any;
  Object.entries(obj).forEach(([key, value]) => {
    res[value as any] = key;
  });
  return res as { [k in S]: T };
};
enum Sex {
  Male = 'male',
  Female = 'female',
}
type SexNum = 0 | 1;

const sexToNum: { [k in Sex]: SexNum } = {
  [Sex.Male]: 0,
  [Sex.Female]: 1,
};

const numToSex = swapFn<Sex, SexNum>(sexToNum);
const male = numToSex[0]; // this type is Sex, not string | number
const female = numToSex[1]; // this type is Sex, not string | number

【讨论】:

    猜你喜欢
    • 2014-05-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-04-12
    • 1970-01-01
    相关资源
    最近更新 更多