如何键入数组的子集以确保它们之间包含完整集合的所有值?

问题描述 投票:0回答:1

如果我有一整套已知值:

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 个)中。

typescript
1个回答
0
投票

对于这样的事情有很多可能的方法。如果您想让您的代码尽可能接近当前版本,那么您只需首先将值分配给变量,并为其类型命名,然后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]]>'.

Playground 代码链接

© www.soinside.com 2019 - 2024. All rights reserved.