从相交类型构造完整类型

Construct complete type from intersected types

我有几种类型,由大量交叉类型组成。我想将这些类型提取到它们最终编译成的内容中。我相信这对选择重构方式会有帮助。

一个例子。鉴于此设置:

type BigType = {
    thing: string
}

type LargeType = {
    blah: boolean
}

type HugeType = BigType & LargeType

我想要某种输出方式:

type HugeType = {
    thing: string
    blah: boolean
}

这是一个小例子 - 实际上还有更多的类型交叉在一起。理想情况下,我会输出这些类型“树”的叶子——最大的结果类型。


有关原因的更多信息。我们有一些复制粘贴的代码,类型、视图(React 组件)和业务逻辑(RxJS 流)都是重复的。速度是必要的,因此复制粘贴了整个文件夹,并针对一组新的功能需求调整了代码。我们现在想减少这种重复。

我们在 RxJS 流和 React 组件树之间将视图模型编码为 Typescript 类型。为了减少重复代码,我们正在讨论的一种方法是将这些视图模型拉入 class 层次结构并在超级 class 中共享公共数据点,改变业务逻辑和视图端的类型。在这些替换模型到位后,下一步将重构双方以减少在相同类型上运行的通用代码。

所以我想从交叉类型构建这些类型(视图模型),因为我认为这将有助于计算出这些不同但非常相似的视图模型之间共享了多少。欢迎就此方法提供反馈。

如果您要交叉的类型只有命名成员(因此它们也不会充当 function types or constructor types) then you can get the behavior you're looking for via a mapped type, which iterates over all named members of a type (even an intersection type) and produces a property for each member, based on a rule. If you make the mapped type an identity map,其中每个 属性 都映射到自身:

type Id<T> = { [K in keyof T]: T[K] };

那么你可以将交集类型转换为等效的单对象类型:

type HugeType = Id<BigType & LargeType>;
/* type HugeType = {
    thing: string;
    blah: boolean;
} */

结果很好,但有时编译器喜欢在 IntelliSense 快速信息中 保留 类型别名,例如 Id。如果您将鼠标悬停在 HugeType 上并且它确实显示 Id<BigType & LargeType>,那将比 BigType & LargeType.

更糟糕

如果你 运行 遇到那个问题,我知道告诉编译器不要保留这些名称的一种方法是使用 conditional type inference 将类型“复制”到新的类型参数中,然后使用类型参数代替原始类型:

type HugeType = (BigType & LargeType) extends infer H ?
  { [K in keyof H]: H[K] } : never;

/* type HugeType = {
    thing: string;
    blah: boolean;
} */

它做的和以前一样;我们正在 BigType & LargeType 上执行恒等映射,但现在不涉及任何名为 Id 的类型别名,因此它不能出现在 IntelliSense 中。


编辑:哦,我知道你在谈论“树”。如果您的意思是对象属性本身具有您可能需要以这种方式合并的属性,那么您可以像这样使用递归定义的标识映射类型(这里条件类型推断确实有帮助):

type IdRecursive<T> = { [K in keyof T]: IdRecursive<T[K]> } extends
  infer I ? { [K in keyof I]: I[K] } : never

并测试它:

type X = {
  a: {
    b: {
      c: number
    }
  }
}
type Y = {
  a: {
    b: {
      d: string
    }
  }
}

type XY = IdRecursive<X & Y>
/* type XY = {
    a: {
        b: {
            c: number;
            d: string;
        };
    };
} */

Playground link to code