Typescript -每个对象至少包含一个类型数组

4sup72z8  于 2023-05-19  发布在  TypeScript
关注(0)|答案(2)|浏览(231)

假设我有3种这样的对象

type mainType = {type:'main', name?:string}
type contentType = {type:'content', text?:string}
type buttonType = {type:'button', name:string}

我想输入一个数组,每个类型至少有一个。假设它会像这样

[
{type:'main'},
{type:'main', name:'Some name'},
{type:'content', text:"Some text"},
{type:'button', name:'Add'},
{type:'button', name:'Submit'}
]

怎么样才能把它打出来?
联合类型不起作用,因为它不会检查该类型是否丢失。
最接近的解决方案是使用扩展操作符?,但是如果我不知道顺序的话就不行了。
这是我的第一个问题,希望它有意义。

4ktjp1zp

4ktjp1zp1#

这是在或过去的边缘,我认为在TypeScript中可以合理地表达。
首先,TypeScript中没有特定的类型type GoodArray = ⋯来表示您正在谈论的内容。
TypeScript具有元组类型,可以表示具有特定数字索引处的特定类型元素的数组。但是你不关心哪个索引保存你的类型的元素,只要它们至少出现一次。这意味着您的需求需要被编写为所有元组类型的无限联合,这些元组类型以您想要的方式工作……比如

type M = MainType; type C = ContentType; type B = ButtonType; 
type SomeType = M | C | B; type S = SomeType;

type GoodArray =
  [M, C, B] | [M, B, C] | [C, M, B] | [C, B, M] | [B, M, C] | [B, C, M] |
  [M, C, B, S] | [M, C, S, B] | [M, B, C, S] | [M, B, S, C] | [M, S, C, B] | [M, S, B, C] |
  [C, M, B, S] | [C, M, S, B] | [C, B, M, S] | [C, B, S, M] | [C, S, M, B] | [C, S, B, M] |
  [B, C, M, S] | [B, C, S, M] | [B, M, C, S] | [B, M, S, C] | [B, S, C, M] | [B, S, M, C] |
  [S, C, B, M] | [S, C, M, B] | [S, B, C, M] | [S, B, M, C] | [S, M, C, B] | [S, M, B, C] |
  [M, C, B, S, S] | [M, C, S, B, S] | [M, C, S, S, B] | [M, B, C, S, S] |
  ⋯

TypeScript不支持无限联合。你可以选择一些相对较小的最大数组长度来支持,这是可行的,因为你只是有一个巨大的联合,而不是一个无限的。这也不太可行。
您可以创建一个generic类型而不是一个特定的类型,它的作用就像对类型的约束,因此T extends GoodArray<T>当且仅当T有效。
然后你还必须创建一个通用的辅助函数goodArray(),它将接受一个参数并验证其类型。因此,您可以编写const arr = goodArray([⋯]);,而不是编写const arr: GoodArray<⋯> = [⋯];并手动写出T类型参数
不幸的是,要做到这一点也不是很简单。泛型类型需要找出缺少了哪些元素类型(如果有的话),然后在某个地方需要它们。一种方法是使用可变元组类型生成递归条件类型来解析输入。就像这样:

type Remainder<T, U> = T extends unknown ? [U] extends [T] ? never : T : never

type GoodArray<T extends any[], U, A extends any[] = []> =
  T extends [infer F, ...infer R] ? GoodArray<R, Remainder<U, F>, [...A, F]> :
  [U] extends [never] ? A : A extends [...infer I, infer L] ? [...I, U, L] : [U, ...U[]];

我不知道这是否值得重点是遍历T,跟踪使用了哪些类型,然后在最后返回数组,或者修改最后一个或两个元素以需要缺少的类型。所以我们会看到:

type GoodExample = GoodArray<[M, C, B], S>
// type GoodExample = [MainType, ContentType, ButtonType] 
type BadExample = GoodArray<[M, B, B], S>
// type BadExample = [MainType, ButtonType, ContentType, ButtonType]

然后是helper函数;理想情况下,我们应该写<T extends GoodArray<T>>(arr: T) => arr,但这是一个非法的循环约束。所以我们需要用推理和变量元组来玩游戏…那么这个:

const goodArray = <T extends SomeType[]>(arr:
  [...T extends GoodArray<T, SomeType> ? T : GoodArray<T, SomeType>]
) => arr;

我们可以测试它,它可以工作:

const ok = goodArray([
  { type: 'main' },
  { type: 'main', name: 'Some name' },
  { type: 'content', text: "Some text" },
  { type: 'button', name: 'Add' },
  { type: 'button', name: 'Submit' }
]); // okay

const bad = goodArray([
  { type: 'main' },
  { type: 'main', name: 'Some name' },
  // { type: 'content', text: "Some text" },
  { type: 'button', name: 'Add' },
  { type: 'button', name: 'Submit' } // error!
  //~~~~ <-- Type '"button"' is not assignable to type '"content"'.
])

const alsoBad = goodArray([]); // error!
//  --------------------> ~~
//  Source has 0 element(s) but target requires 1.

但这很复杂
最后,所有这些都只允许您验证开发人员编写的某些特定数组文字。编译器不“理解”约束。如果你有一个接受GoodArrayGoodArray<T>的函数,它不会真正“知道”,例如,ButtonType肯定存在:

function wontKnow<T extends SomeType[]>(arr: T) {
  const x = arr.find((x): x is ButtonType => x.type === "button").name; // error!
  //  --->  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  // Object is possibly 'undefined'.
}

find() array method返回一个可能的undefined值。因此,您会发现自己要么需要进行额外的运行时检查,要么需要使用类型Assert来避免此类错误。
总之,这一切意味着:我不会真的尝试依赖类型系统来做这类事情,或者至少我会非常清楚所涉及的限制。
Playground链接到代码

irlmq6kh

irlmq6kh2#

我不知道这对你来说是否是一个解决方案,但你可以像这样输入你的数组:

type TypeArray = [mainType, contentType, buttonType, ...(mainType | contentType | buttonType)[]]

但这要求数组的前三个元素的顺序正确。

相关问题