我试图构造一个类型S
,它是一个离散的接口数量的(部分)总和(在下面的例子中,A
和B
),即:满足{} | A | B | (A & B)
。
我尝试了以下方法(使用预期值),但没有达到预期效果(很可能是因为我不知道如何表示除{}
以外的空接口,我认为实际情况并非如此)。
type A = {
readonly a1: 1
}
type B = {
readonly b1: 1
readonly b2: 2
}
type Maybe<T> = T | {}
type S = Maybe<A> & Maybe<B>
// algebraic type: C = ({} & {}) | (A & {}) | (A & B) | ({} & B)
const value1: S = {} // should type-check (value1 contains neither A nor B)
const value2: S = {a1: 1} // should type-check (value2 contains A)
const value3: S = {b1: 1, b2: 2} // should type-check (value3 contains B)
const value4: S = {a1: 1, b1: 1, b2: 2} // should type-check (value4 contains A and B)
const value5: S = {d: 1} // ideally, should NOT type-check (`d` is an extraneous key)
const value6: S = {a1: 2} // should NOT type-check (value6 does not satisfy A)
这一点能否实现?
请注意,我曾考虑使用Partial
,但它并不能完全解决我的问题,因为(Partial<A> & Partial<B>) !== ({} | A | B | (A & B))
。
1条答案
按热度按时间vzgqcmou1#
TypeScript中的对象类型通常是 open 和 extendible,并且允许值具有额外的属性。它们不是microsoft/TypeScript#12936中要求的 * 封闭 、 密封 * 或 “精确”。这种开放性通常非常有用,允许
interface
层次结构和class
层次结构形成 type 层次结构。很好的是,子/超接口和子/超类也是子/超类型,如果你不能在不破坏类型的情况下在子接口或子类中添加属性,这将是令人讨厌的。因此,例如,空类型
{}
并不意味着“没有 * 允许 * 属性的对象”;它的意思是“没有 * 已知 * 属性的对象”。所以{}
将接受 * 几乎任何东西 *,(甚至是原始类型,因为它们是自动装箱的),除了null
和undefined
。(To TypeScript在object literals上执行过多的属性检查,原因与类型安全无关。所以
const a: {x: number} = {x: 0, y: 1}
会给予你一个错误,但是const b = {x: 0, y: 1}; const a: {x: number} = b;
不会给你一个错误。还有弱类型检测,它对具有所有可选属性的类型做类似的事情。因此,从技术上讲,要实现您所要求的内容,语言需要支持确切的类型。除非发生这种情况,否则我们必须尝试模拟或近似它。
一般来说,我们可以得到的最接近“没有 * 额外 * 属性的对象类型”是“没有 * 特定 * 属性集的对象类型”。嗯,即使是这样也不太可能。你能得到的最接近的结果更像是“一个对象类型,其中这个特定集合中的每个属性要么缺失,要么存在,但-
undefined
”。这可以通过一个mapped type来实现,它接受您想要禁止的所有键K
,并使它们成为不可能的never
类型的可选属性,如如此给予
我们有一个禁止在
b1
或b2
键上定义任何属性的类型。回到
Maybe<T>
。而不是T | {}
,我们可以说T | ProhibitKeys<keyof T>
:因此,
Maybe<T>
要么具有T
的 * 所有 *(必需)属性,要么 * 没有 *T
的属性。现在,如果我们将S
定义为并使用How can I see the full expanded contract of a Typescript type?中描述的技术检查其结构:
您可以看到
S
具有四种有效的属性组合,并导致以下行为:我想这些都是你想要的。您还可以在这里获得所需的错误:
但这是因为弱类型和过多属性检测,并且是脆弱的。你可能会惊讶于
这会使弱类型检测(因为需要
x
)和过多的属性检查(因为value5a
不是对象文字)失效。但这真的是我们能做的最好的了;归根结底,TypeScript * 没有确切的类型 *,所以像这样的近似值应该足够了。Playground链接到代码