为什么TypeScript`Exclude <T,U>`不排除?这是错误吗?

时间:2019-07-04 16:30:53

标签: typescript union-types

这很难解释,但是我试图在对象类型的子字段中过滤出某种类型。

Exclude<T, U>似乎不适用于这种嵌套:

type PrivateName = { kind: "PrivateName" };
type Identifier = { kind: "Identifier" };

type NamedNode = {
  name: PrivateName | Identifier;
};

type PrivateNamedNode = {
  name: PrivateName;
};

declare const privateName: PrivateName;

type NamedNodeNonPrivate = Exclude<NamedNode, PrivateNamedNode>;

// I expected this next line to error, but it doesn't!
const x: NamedNodeNonPrivate = { name: privateName };

为什么最后一行没有出错?我应该打开错误报告吗?

在TS版本上测试:3.5.1、3.3.3、3.1.6和3.0.1。 playground link

潜在的根本原因

Exclude<T, U>的定义是

/**
 * Exclude from T those types that are assignable to U
 */
type Exclude<T, U> = T extends U ? never : T;

在我的示例中,这似乎是惰性的,因为NamedNode不扩展{ name: PrivateName }

按如下所示更改定义会产生所需的错误消息。不幸的是,这不是重构,我可以手动处理整个代码库:

type NamedNode = {
  name: PrivateName
} | {
  name: Identifier
}

Curry-Howard同构告诉我们乘积类型像合取,求和类型像析取。

以下内容适用于布尔逻辑:

A & (B or C) implies (A & B) or (A & C)

因此,NamedNode的两个定义相等是合理的。这是缺乏分布性吗?

  • 1 我上述问题的原因
  • 2 是一个错误?

1 个答案:

答案 0 :(得分:1)

{ name: PrivateName | Identifier; };{ name: PrivateName } | { name: Identifier }仅在对象类型仅包含此属性的情况下才等效。常见的用例是联合的不同分支将拥有不同的类型。

无论如何,条件类型会distribute,但只能覆盖裸类型参数。每种类型的属性都不会发生魔术分布。并且由于关系NamedNode extends PrivateNamedNode不成立,因此不会排除任何类型(由于NamedNode不是联合,因此不能排除任何类型,即使关系成立,您也会得到never,因为那么整个类型将被排除,您将一无所有)。

我们可以构建一个从对象属性中排除类型的类型:

type PrivateName = { kind: "PrivateName" };
type Identifier = { kind: "Identifier" };

type NamedNode = {
    name: PrivateName | Identifier;
};

type PrivateNamedNode = {
    name: PrivateName;
};

declare const privateName: PrivateName;

type ExcludePropertyTypes<T, TExclude extends Partial<T>> {
    [P in keyof T]: Exclude<T[P], TExclude[P]>
}

type NamedNodeNonPrivate = ExcludePropertyTypes<NamedNode, PrivateNamedNode>;

// Errors now (as intended)
const x: NamedNodeNonPrivate = { name: privateName };

Playground link