如果我有一整套已知值:
const fullSet = [1, 2, 3, 4] as const
type FullSet = (typeof fullSet)[number] // type FullSet = 1 | 3 | 2 | 4
我想要 3 个子集,它们之间包含完整集合中的所有值。
例如,我尝试过:
type S<S1, S2> = Readonly<Array<Exclude<FullSet, S1 | S2>>>
const subset1: S<Subset2, Subset3> = [1] as const
type Subset1 = (typeof subset1)[number]
const subset2: S<Subset1, Subset3> = [2, 3] as const
type Subset2 = (typeof subset2)[number]
const subset3: S<Subset1, Subset2> = [4] as const
type Subset3 = (typeof subset3)[number]
但这自然会导致循环依赖,即(例如):
'subset1' is referenced directly or indirectly in its own type annotation.
Type alias 'Subset1' circularly references itself.
我正在寻找这样的东西,当向完整集合中添加新值时,会键入错误,表明新值不包含在 3 个子集中的 1 个(且仅 1 个)中。
对于这样的事情有很多可能的方法。如果您想让您的代码尽可能接近当前版本,那么您只需首先将值分配给变量,并为其类型命名,然后then作为单独的检查来打破循环性步。 例如:
type Check<T extends U, U extends V, V = T> = void;
const subset1 = [1] as const
type Subset1 = (typeof subset1)[number]
type CheckSubset1 = Check<readonly Subset1[], S<Subset2, Subset3>>
const subset2 = [2, 3] as const
type Subset2 = (typeof subset2)[number]
type CheckSubset2 = Check<readonly Subset2[], S<Subset1, Subset3>>
const subset3 = [4] as const
type Subset3 = (typeof subset3)[number]
type CheckSubset3 = Check<readonly Subset3[], S<Subset1, Subset2>>
Check<T, U>
实用程序类型实际上不会返回任何有用的东西,但如果您使用不相互扩展的Check<T, U>
和T
调用U
,它会抱怨。这是一种平等检查。 因此,仅当 CheckSubset1
和 readonly Subset1[]
可以相互分配时,S<Subset2, Subset3>
的行才会编译。 与 CheckSubset2
和 CheckSubset3
线相同。如果您在 fullSet
上添加或删除成员,您会在适当的位置收到错误消息,告诉您问题所在:
const fullSet = [1, 2, 3, 4, 10] as const // added 10 here
type CheckSubset1 = Check<readonly Subset1[], S<Subset2, Subset3>> // error!
// -----------------------------------------> ~~~~~~~~~~~~~~~~~~~
// Type '10' is not assignable to type '1'.
另一方面,如果您要经常执行此类操作,您可能需要编写一个可重用的辅助函数或类或类型,将联合划分为详尽且独占的元组类型。 同样,有很多方法可以实现它:
class Partition<const T, U extends (readonly any[])[] = []> {
constructor(public members: T[], public u: U = [] as any as U) { }
sub<const V extends readonly T[]>(v: V): Partition<Exclude<T, V[number]>, [...U, V]> {
return new Partition(this.members as any, [...this.u, v])
}
end(this: Partition<never, U>): U {
return this.u;
}
}
const [subset1, subset2, subset3] =
new Partition([1, 2, 3, 4]).sub([1]).sub([2, 3]).sub([4]).end();
现在将原始数组传递给
new Partition
,然后可以调用 sub()
直到用完元素,最后调用 end()
取出子集。如果你重复使用元素,或者太早调用 end()
,你会得到错误:
const [subset1, subset2, subset3] =
new Partition([1, 2, 3, 4, 5]).sub([1]).sub([2, 3]).sub([4]).end(); // error!
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// The 'this' context of type 'Partition<5, [readonly [1], readonly [2, 3], readonly [4]]>'
// is not assignable to method's 'this' of type
// 'Partition<never, [readonly [1], readonly [2, 3], readonly [4]]>'.