如何检查 TypeScript 中产品类型的详尽性?

How to check for exhaustiveness of product types in TypeScript?

在 TypeScript 中检查求和类型的详尽性非常容易。

type Sum =
    | { tag: 'num'; value: number }
    | { tag: 'str'; value: string };

const len = (sum: Sum): number => {
    switch (sum.tag) {
        case 'num': return sum.value;
        case 'str': return sum.value.length;
        default: {
            const unhandled: never = sum;
            throw new Error(`Unhandled sum ${unhandled}`);
        }
    }
};

现在,如果我向 Sum 类型添加新变体,则 sum 将不再可分配给 unhandled。因此,我们将因非详尽性而出现编译时错误。

如何在 TypeScript 中对产品类型执行相同的操作?考虑以下示例。

type Product = {
    num: number;
    str: string;
};

const repeat = (product: Product): string => {
    const { num, str } = product;
    return str.repeat(num);
};

现在,如果我向 Product 类型添加一个新的 属性,那么我希望 TypeScript 编译器报告一个非详尽性错误,因为新的 属性 没有'没有被解构和使用。我该怎么做?

如果代码因非详尽性而抛出运行时错误,则加分。

让我们从抛出一个运行时错误开始,因为它不详尽。我们可以通过解构其余属性来实现这一点,如果它有一个或多个可枚举键则抛出错误。

const repeat = (product: Product): string => {
    const { num, str, ...props } = product;
    if (Object.keys(props).length > 0) {
        throw new Error(`Unhandled props ${props}`);
    }
    return str.repeat(num);
};

接下来,为了让 TypeScript 在编译时检查穷尽性,我们可以执行以下操作。

const repeat = (product: Product): string => {
    const { num, str, ...props } = product;
    const unhandled: {} extends typeof props ? {} : never = props;
    if (Object.keys(unhandled).length > 0) {
        throw new Error(`Unhandled props ${unhandled}`);
    }
    return str.repeat(num);
};

这是它的工作原理。

  1. 空对象类型 {} 只能分配给 typeof props 当且仅当 props 是一个空对象。
  2. 因此,当props为空对象时,unhandled的类型为{},一切正常。
  3. 然而,当 props 不是空对象时,unhandled 的类型是 never,我们会得到一个编译时错误。

因此,上述代码将在解构时检查产品类型的详尽性。如果将新的 属性 添加到 Product 类型,则 props 将不再可分配给 unhandled 并且我们将因非详尽性而出现编译时错误。

此外,您可以启用@typescript-eslint/no-unused-vars 规则以确保使用所有解构的属性。确保将 ignoreRestSiblings 选项设置为 false