我有一个函数,它获取一个映射并生成映射的双射:
export function keyTextBijection(map) {
const bijection = {};
Object.keys(map).forEach(key => {
bijection[key] = map[key];
bijection[map[key]] = key;
});
return bijection;
}
如何在TypeScript中优雅地为函数签名?
它有助于编写一个类型函数,将类似{a: "A", b: "B"}
的类型转换为其逆类型{A: "a", B: "b"}
。TypeScript 4.1将通过microsoft/TypeScript#40336:实现的映射类型as
子句使这一点变得容易
// TS4.1+
type Invert<T extends Record<keyof T, PropertyKey>> = { [K in keyof T as T[K]]: K };
但对于TypeScript 4.0及以下版本,在大多数情况下,您可以通过使用映射的条件类型来获得相当等效的内容:
// TS4.0-
type Invert<T extends Record<keyof T, PropertyKey>> =
{ [K in T[keyof T]]: { [P in keyof T]: K extends T[P] ? P : never }[keyof T] }
在任何一种情况下,都希望keyTextBijection
接受T
类型的对象,并返回T & Invert<T>
类型的对象。由于这样的交集看起来很难看,我通常定义一个名为Id
的无操作映射类型,它可以将交集合并为一个类型对象。(这个Id
在这个问题中也被称为Expand
(:
type Id<T> = T extends infer U ? { [K in keyof U]: U[K] } : any;
所以现在我们可以给keyTextBijection
:一个呼叫签名
function keyTextBijection<T extends Record<keyof T, S>, S extends PropertyKey>(
map: T
): Id<T & Invert<T>>;
function keyTextBijection(map: any) {
const bijection: any = {};
Object.keys(map).forEach(key => {
bijection[key] = map[key];
bijection[map[key]] = key;
});
return bijection;
}
在上文中,S
实际上并没有做任何用于推理目的的事情;这主要只是给编译器一个提示,提示它应该尝试将T
类型推断为从文字到文字的映射。当您输入{a: "A"}
作为map
参数时,您希望编译器将类型视为{a: "A"}
,而不是{a: string}
,因为后者不会正确反转。
该函数使用单个重载调用签名,以允许实现在类型安全方面松懈,并使用any
。(如果不这样做,您将发现自己不得不使用一堆类型断言,因为编译器将无法验证赋值语句的类型安全性(。
让我们测试一下:
const bij = keyTextBijection({ a: "A", b: "B", c: "C" });
/* const bij: {
a: "A";
b: "B";
c: "C";
A: "a";
B: "b";
C: "c";
} */
console.log(bij);
/* {
"a": "A",
"A": "a",
"b": "B",
"B": "b",
"c": "C",
"C": "c"
} */
bij
的类型和值在我看来是正确的。
到代码的游乐场链接