typescript - TypeScript 中任意数量类型的非析取联合

标签 typescript typescript-generics

在 TypeScript 中,我想创建一个联合类型来表示属于一个或多个不同类型的值,类似于 oneOfOpenAPIJSON Schema .根据a previous answer on a similar question ,TypeScript 中的联合运算符应该执行此操作,因为它表示集合联合(包含或)。但是,当 using the in operator as a type guard 时,这与 TypeScript 中的类型推断行为不匹配。 ,它似乎假设值完全属于联合类型之一(析取联合或异或)。作为一个简单的例子,这个 leads to a type error :

interface SoftwareDeveloper { code(): void; }
interface Chef { cook(): void; }
type SoftwareDeveloperOrChef = SoftwareDeveloper | Chef;

// Determined at runtime. May have the code method, the cook method, or both.
const person = { code: () => { }, cook: () => { } };

const softwareDeveloperOrChef: SoftwareDeveloperOrChef = person;
if ("code" in softwareDeveloperOrChef) {
    // softwareDeveloperOrChef has inferred type SoftwareDeveloper
    softwareDeveloperOrChef.code();
    // Coding is hungry work
    if ("cook" in softwareDeveloperOrChef) {
        // Not allowed, since softwareDeveloperOrChef has inferred type never
        softwareDeveloperOrChef.cook();
    }
}

为了获得我想要的行为和avoid type errors ,我必须手动将交集类型的所有组合添加到联合中:

type SoftwareDeveloperOrChefOrBoth = SoftwareDeveloper | Chef | (SoftwareDeveloper & Chef);
const softwareDeveloperOrChefOrBoth: SoftwareDeveloperOrChefOrBoth = person;
if ("code" in softwareDeveloperOrChefOrBoth) {
    // softwareDeveloperOrChef has inferred type SoftwareDeveloper | (SoftwareDeveloper & Chef)
    softwareDeveloperOrChefOrBoth.code();
    if ("cook" in softwareDeveloperOrChefOrBoth) {
        // Allowed, since softwareDeveloperOrChefOrBoth has inferred type SoftwareDeveloper & Chef
        softwareDeveloperOrChefOrBoth.cook();
    }
}

一个中间问题可能是这是否是我应该期望的行为?但是,考虑到它是已实现的行为,我实际上对为任意数量的类型构造非析取联合类型的方法更感兴趣。随着类型数量的增加,手动执行此操作会导致联合类型定义的大小呈指数增长:

type AorB = A | B | A & B;
type AorBorC = A | B | C | A & B | A & C | B & C | A & B & C;
type AOrBOrCorD = A | B | C | D | ... | B & C & D | A & B & C & D;

我可以为特定数量的参数编写通用类型:

type AnyOf2<A, B> = A | B | A & B;
type AnyOf3<A, B, C> = AnyOf2<A, AnyOf2<B, C>>;
type AnyOf4<A, B, C, D> = ...;

但是我可以使用类似于 AnyOf<A | B> 中使用的技术来制作一个采用任意数量类型的类似泛型类型,例如联合 ( UnionToIntersection )实用类型 implemented here

最佳答案

出现错误的原因是使用联合类型不能正确地允许 SoftwareDeveloperOrChef两者都是 SoftwareDeveloperChef .如果我们知道对象是 SoftwareDeveloper那么我们根本没有关于 cook 的任何信息属性(property)——可以是任何东西。它不必是预期类型的​​函数。

您的 type AnyOf2<A, B> = A | B | A & B; 就快完成了,但是当我们处理对象的属性时,我们可以使用 PartialRequired以获得更好的类型。

我们希望为 SoftwareDeveloperOrChefOrBoth 实现的最终类型是cookcode都是可选函数,但至少有一个是必需的。

试试这个:

type EitherOrBoth<A, B> = Partial<A & B> & (A | B);

type SoftwareDeveloperOrChef = EitherOrBoth<SoftwareDeveloper, Chef>;

一个变化是使用Partial你不能再使用 "code" in softwareDeveloperOrChef作为类型保护,因为对象可以将属性键显式设置为 undefined .例如{code: undefined; cook: () => {};}是我们类型的有效成员。

但是这段代码 check out

const softwareDeveloperOrChef: SoftwareDeveloperOrChef = person;
if (softwareDeveloperOrChef.code) {
    softwareDeveloperOrChef.code();
    if (softwareDeveloperOrChef.cook) {
        // this is allowed now
        softwareDeveloperOrChef.cook();
    }
}

编辑

根据我们在评论中的讨论,您需要单独的接口(interface) SoftwareDeveloperChef有多种方法,您希望能够确定 Chef 中的一种方法是否有效存在,Chef 的所有其他方法也将出席。

为了最大限度的类型安全,组合 SoftwareDeveloperOrChefOrBoth type 应该明确声明任何成员必须拥有 Chef 的所有方法或者没有。

此实用程序类型 Not<T>通过说 T 的任何键不能设置为 undefined 以外的任何值来处理此问题:

export type Not<T> = {
    [P in keyof T]?: never;
};

请注意,仅不包括该属性并不表示无法设置它。如 linked thread 中所述, 和你的 type AorB当创建一个新的无效对象(一个具有 SoftwareDeveloper 的所有方法和一些但不是所有 Chef 的方法)时,您会得到一个错误,但在将类型分配给现有对象时不会,因为这个无效对象可分配给A因此也可以分配给 AorB .

而不是宽松的 EitherOrBoth<A,B>之前建议的类型,这里是一个更严格的强制排他性的类型:

type ExclusiveAndOr<A, B> = A & Not<B> | B & Not<A> | A & B;

type SoftwareDeveloperOrChef = ExclusiveAndOr<SoftwareDeveloper, Chef>;

有了这个类型,就不再只是假设一个人可以cook他们也可以chop .

function checkPerson( softwareDeveloperOrChef: SoftwareDeveloperOrChef ) {
    if (softwareDeveloperOrChef.code) {
        softwareDeveloperOrChef.code();
        if (softwareDeveloperOrChef.cook) {
            // should be ok
            softwareDeveloperOrChef.cook();
            // ok to assume all methods exist
            softwareDeveloperOrChef.chop();
        } else {
            // should be error
            softwareDeveloperOrChef.chop();
        }
    }
}

Typescript Playground Link

关于typescript - TypeScript 中任意数量类型的非析取联合,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/62698675/

相关文章:

javascript - 如何在不打开打印机设置页面的情况下在 ionic 应用程序中使用 print.js 进行打印

typescript - 类方法的条件返回类型取决于构造函数中的选项

具有通用类型的 Typescript 包装函数

typescript - 根据参数从映射类型返回类型

javascript - 在 Ionic 2 中点击 google map 时出现 "Uncaught TypeError: ele.hasAttribute is not a function"

typescript - 在 typescript 中扩展枚举

typescript - 为什么我不能在 TypeScript 中将 `typeof x` 与 "null"或 "MyClass"进行比较?

javascript - Typescript X 不是一个函数

typescript - 如何使用 Typescript 编写具有扩展可迭代约束的泛型类型

javascript - TypeScript 类型推断 - 函数的通用对象