如何从联合创建类型数组类型

How to create types array types from unions

这个问题对我来说有点难以表达,这可能是我没有找到任何研究的原因。

我正在尝试获得一个最终只有数组的类型。我总是想以“一级”数组结束(如果给出数组的数组,我不关心输出。这在我的情况下不会发生)。所以想要的结果如下:

type T1 = ForceArray<string> // wanted: string[]

type T2 = ForceArray<string[]> // wanted: string[]

我可以用下面的代码来做到这一点:

type ForceArray<T> = NonNullable<T> extends Array<any> ? T : Array<T>
这里使用

NonNullable是因为在实际用例中,ForceArray<T>用于可选的接口属性。这意味着 T 可以是 undefined,或者换句话说 T = U | undefined,其中 U 是实际类型(显示的 U = stringU = string[]示例)。

上面的类型定义很好用,联合类型除外。

type T3 = ForceArray<string | string[]> // shows (string | string[])[] but I want string[]

其他联合类型也应转换为数组,这也不起作用:

type T4 = ForceArray<number | string[]> // wanted: number[] | string[]

type T5 = ForceArray<number[] | string> // wanted: number[] | string[]

在打字稿文档中,他们通过谈论 Distributive conditional types 与此相关。但据我了解文档,我的代码应该可以正常工作。所以很明显我没有正确理解文档。

有没有办法获得预期的行为?


附加信息:

产生输出的实际代码非常简单,这就是我认为应该有解决方案的原因:

const forceArray = (value) => Array.isArray(value) ? value : [value];

此函数在这样的上下文中使用,其中 obj 可以实现任何接口(具有可选属性):

const convertObjectValuesToArray = (obj) => {
    const newObj = {};
    for (const key of obj) {
        newObj[key] = forceArray(obj[key]);
    }
    return newObj;
}

NonNullable 似乎是这里的问题,因为它禁用了类似于括号 [] 的通用类型的可分配性。我们可以删除它并用它代替 returned 类型。

type ToArray<Type> = Type extends Array<any> ? NonNullable<Type> : NonNullable<Type>[]

type A = ToArray<string>              // string[]
type B = ToArray<string[]>            // string[]
type C = ToArray<string | number[]>   // string[] | number[]
type D = ToArray<string | null>       // string[] | never[]

这里唯一的问题是 D,其中 return 类型是 string[] | never[]。但是我们可以将整个东西包装成一个 Exclude 来摆脱 never

type ToArray<Type> = Exclude<Type extends Array<any> ? NonNullable<Type> : NonNullable<Type>[], never[] | never>

type A = ToArray<string>              // string[]
type B = ToArray<string[]>            // string[]
type C = ToArray<string | number[]>   // string[] | number[]
type D = ToArray<string | null>       // string[]