所谓的“空对象类型”{}很广泛;几乎所有的值都可以分配给它。粗略地说,它将接受任何可以索引到 like 对象中的值。这包括所有非原始类型,如object,但也包括five primitive types with wrapper objects:所以string、number、boolean、bigint 和symbol 可分配给{},因为值当您像访问对象一样访问它们上的成员时,其中的一些类型会自动包装在 String、Number、Boolean、BigInt 和 Symbol 对象中。
从某种意义上说,这意味着您无法更改的 ExternalFoo 类型是多余的:{} | string | number 基本上等同于 {}(但两者的显示方式不同,并且在某些情况下编译器可能会以不同方式处理)。我想知道ExternalFoo 的实际意图是否应该更像object | string | number 而不是{} | string | number。例如,它真的是要接受boolean吗?
const wasThisIntended: ExternalFoo = true;
但你说你不能改变它,所以这超出了范围。
TypeScript 没有否定类型(请参阅 microsoft/TypeScript#29317),因此您不能使用 ExternalFoo 并快速生成拒绝 string 的版本。像 ExternalFoo & not string 这样的类型在 TypeScript 中根本无法表示,因为它目前存在。 Exclude utility type 仅过滤联合。虽然{} | number | string 可以被过滤以产生{} | number,但{} 本身不能被过滤,因为它不是联合体。
为了从ExternalFoo 中排除string,我们不仅需要从定义中删除string,还需要将{} 更改为也排除string 的内容。 {} 类型或多或少与 object | string | number | boolean | bigint | symbol 相同。如果需要,我们可以从 that 中删除 string,以生成
type Foo = object | number | boolean | bigint | symbol;
那么让我们试试吧!
const foo1: Foo = {}; // okay
const foo2: Foo = 5; // okay
const foo3: Foo = "bar"; // error!
看起来不错。根据需要,您可以将{} 和5 分配给Foo 类型的变量,但不能将"bar" 分配给它。这仍然会发生:
const stillHappens: Foo = true; // okay
但是boolean 不是string,所以我想它也是需要的。
对于您的较长版本,我建议尝试将泛型类型 T extends ExternalFoo 转换为 T & Foo 并查看它是否适合您:
type HasLength = { length: number };
let l: HasLength;
l = [1, 2, 3]; // okay
l = "hello"; // okay
l = { length: 19 }; // okay
type NotString<T extends ExternalFoo> = T & Foo;
type HasLengthNotString = NotString<{ length: number }>;
let n: HasLengthNotString;
n = [1, 2, 3]; // okay
n = "hello"; // error
n = { length: 19 }; // okay
Playground link to code