首页 > 解决方案 > 如何编写在 TypeScript 中生成双射的函数的函数签名?

问题描述

我有一个函数,它需要一张地图并生成地图的双射:

 export function keyTextBijection(map) {
  const bijection = {};
  Object.keys(map).forEach(key => {
    bijection[key] = map[key];
    bijection[map[key]] = key;
  });
  return bijection;
}

如何在 TypeScript 中优雅地签名函数?

标签: 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参数输入时,您希望编译器将类型视为 as{a: "A"}而不是 as {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对我来说是正确的。


Playground 代码链接


推荐阅读