如何键入具有相应类型的元组数组?

Posted

技术标签:

【中文标题】如何键入具有相应类型的元组数组?【英文标题】:How to type tuple array with corresponding types? 【发布时间】:2022-01-06 03:31:49 【问题描述】:

谁能帮我解决这个不寻常(我认为)的问题?

最初,我正在实现类型化 Map。

export enum KeyType 
  aa = 'aa',
  bb = 'bb'


export interface ValueTypes 
  aa: boolean,
  bb: string


interface TypedMap extends Map<KeyType, ValueTypes[KeyType]> 
  get<TK extends KeyType>(k: TK): ValueTypes[TK] | undefined
  set<TK extends KeyType>(k: TK, v: ValueTypes[TK]): this

上面的代码运行良好。然后我想实现一个能够为这个地图设置多个值的函数:

function setMany<TKey, TVal> (
  map: Map<TKey, TVal>,
  change: (
    Map<TKey, TVal> |
    [TKey, TVal][]
  )
): void 
  const entries = change instanceof Map ? change.entries() : change

  for (const [key, value] of entries) 
    map.set(key, value)
  

如何输入[TKey, TVal][] 元组数组,以便对输入进行相应的类型检查?喜欢:

const tm = new Map() as TypedMap

setMany(
  tm,
  [
    [KeyType.aa, 'Foo'], // Error, aa requires boolean
    [KeyType.bb, 'Bar'] // Nice, bb requires string
  ]
)

据我了解,我需要这样的东西:

type ChangeTuple<TK extends KeyType> = [TK, ValueTypes[TK]]

但要使用参数和数组。我试过这个:

interface setManyV2 
  (
    map: TypedMap,
    change: (
      TypedMap |
      ChangeTuple[] // requires Generic type, but ChangeTuple<KeyType>[] doesn't work because TK doesn't extend anymore
    )
  ): void

测试用例:

setMany(
  tm,
  [
    [KeyType.aa, true], // correct, aa requires boolean
    [KeyType.aa, false], // correct, aa requires boolean
    [KeyType.bb, 'any string'], // correct bb requires string
    [KeyType.aa, undefined], // incorrect, aa requires boolean, not undefined
    [KeyType.aa, new Set()], // incorrect, aa requires boolean, not Set
    [KeyType.aa, 'any string'], // incorrect, aa requires boolean, not string
    [KeyType.bb, new Set()], // incorrect, aa requires string, not Set
    [KeyType.bb, undefined], // incorrect, aa requires string, not undefined
    [KeyType.bb, false], // incorrect, aa requires string, not boolean
    [KeyType.bb, true] // incorrect, aa requires string, not boolean
  ]
)

const tm2 = new Map() as TypedMap

tm2.set(KeyType.aa, true)
tm2.set(KeyType.bb, 'string')

setMany(
  tm,
  tm2
)

【问题讨论】:

您的第一个示例导致错误 @captain-yossarian 谢谢,已修复 如果在setManyV2接口中使用ChangeTuple&lt;KeyType&gt;[]会怎样? @Dahou 然后它将接受[KeyType.aa | KeyType.bb, boolean | string] 用于所有不可接受的元组 - 我需要[KeyType.aa, boolean][KeyType.bb, string] 之一。 【参考方案1】:

我假设您希望通过TypedMap 重载您的Map 实现。如果是,有更好的方法。

为了重载Map,您需要与Map 类型相交,如下所示:Map&lt;'a', boolean&gt; &amp; Map&lt;'b', string&gt;

既然你有KeyTypeValueTypes,我们可以把它们合并成一个数据结构:


type MapOverloading<Keys extends string, Values extends Record<Keys, unknown>> = 
    [Prop in Keys]: Map<Prop, Values[Prop]>



// type TypedMap = 
//     aa: Map<KeyType.aa, boolean>;
//     bb: Map<KeyType.bb, string>;
// 
type TypedMap = MapOverloading<KeyType, ValueTypes>

现在为了产生 Map 重载,我们需要获得 TypedMap 对象值的联合。 很简单,只要使用这个工具:

type Values<T> = T[keyof T]

现在我们需要将联合转换为交集 (UnionToIntersection):

// credits goes to https://***.com/a/50375286
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (
    k: infer I
) => void
    ? I
    : never;

type MapOverloading<Keys extends string, Values extends Record<Keys, unknown>> = 
    [Prop in Keys]: Map<Prop, Values[Prop]>


type Values<T> = T[keyof T]

type TypedMap = UnionToIntersection<Values<MapOverloading<KeyType, ValueTypes>>>

更多关于创建动态重载的信息你可以找到here TypedMap 是一个重载的 Map 数据结构。让我们测试一下:

const tm: TypedMap = new Map();

tm.set(KeyType.aa, true) // ok
tm.set(KeyType.bb, true) // expected error

考虑这个例子:

export enum KeyType 
    aa = 'aa',
    bb = 'bb'


export interface ValueTypes 
    aa: boolean,
    bb: string



// credits goes to https://***.com/a/50375286
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (
    k: infer I
) => void
    ? I
    : never;

type MapOverloading<Keys extends string, Values extends Record<Keys, unknown>> = 
    [Prop in Keys]: Map<Prop, Values[Prop]>


type Values<T> = T[keyof T]

type TypedMap = UnionToIntersection<Values<MapOverloading<KeyType, ValueTypes>>>

const tm: TypedMap = new Map();

tm.set(KeyType.aa, true) // ok
tm.set(KeyType.bb, true) // expected error

type IsNever<T> = [T] extends [never] ? true : false

type TupleToMap<Tuple extends any[], ResultMap extends Map<any, any> = never> =
    Tuple extends []
    ? ResultMap
    : Tuple extends [[infer Key, infer Value], ...infer Rest]
    ? IsNever<ResultMap> extends true ? TupleToMap<Rest, Map<Key, Value>> : TupleToMap<Rest, ResultMap & Map<Key, Value>>
    : never

type Validation<Tuple extends any[], CustomMap> = CustomMap extends TupleToMap<Tuple> ? Tuple : []

function setMany<
    Key extends string,
    Value,
    CustmoMap extends Map<Key, Value>,
    Tuple extends [Key, Value],
    Change extends Tuple[],
    >(
        map: CustmoMap,
        change: Validation<[...Change], CustmoMap>,
): void 

    for (const [key, value] of change) 
        map.set(key, value)
    


setMany(
    tm,
    [
        [KeyType.aa, true], // ok
        [KeyType.aa, false] // ok
    ]
)

setMany(
    tm,
    [
        [KeyType.bb, 's'], // ok
        [KeyType.bb, 'hello'], // ok
    ]
)

setMany(
    tm,
    [
        [KeyType.bb, 's'], // ok
        [KeyType.aa, true, // ok
    ]
)

setMany(
    tm,
    [
        [KeyType.aa, undefined] // expected error
    ]
)

setMany(
    tm,
    [
        [KeyType.aa, new Set()] // expected error
    ]
)

setMany(
    tm,
    [
        [KeyType.bb, undefined] // expected error
    ]
)

setMany(
    tm,
    [
        [KeyType.bb, new Set()] // expected error
    ]
)

setMany(
    tm,
    [
        [KeyType.bb, false] // expected error
    ]
)
setMany(
    tm,
    [
        [KeyType.bb, true] // expected error
    ]
)

setMany(
    tm,
    [
        [KeyType.aa, 'any string'] // expected error
    ]
)

Playground 以上示例回答了您的第一个问题:如何使其与元组一起使用。

但是有一个缺点,如果元组元素中的一个无效,整个元组会被高亮显示

请让我知道它是否适合您。如果是,我会提供更多解释。

【讨论】:

哇,有趣。但是 setMany 函数将 TKey 检测为 KeyType.bb 并将 TVal 检测为字符串,并且不接受 aa 和 boolean 元组。似乎它只适用于交叉路口的一种地图类型。 @ФилиппФастер 请提供测试用例。你期望什么工作,你期望什么失败。对我来说会更容易 setMany(tm, [[KeyType.aa, true], [KeyType.aa, false], [KeyType.bb, 'anystring']])。所以它应该接受具有 KeyType 及其对应的 ValueTypes 类型的元组。我尝试自己修复它,但我坚持同样的误解 - 我不知道如何正确输入元组:) @ФилиппФастер 更新了元组。您是否希望第二个参数也是地图?如果是,它应该是什么类型的地图?和第一个参数一样吗? 哇,抱歉耽搁了这么久。这个解决方案很棒!非常感谢!

以上是关于如何键入具有相应类型的元组数组?的主要内容,如果未能解决你的问题,请参考以下文章

如何取消嵌套混合类型的元组? [复制]

具有多个任意但相等类型的元组

MDX:如何将具有多个成员的元组转换为具有单个成员的元组?

如何从具有子元组的元组创建列表?

Swift中的元组,数组,字典

Swift 元组(Tuple)