从数组构造 Map 时“没有重载匹配此调用”

时间:2021-05-21 05:51:49

标签: typescript

new Map([[1, [2, 3]] ].map(e => e[1])); 是完全有效的 javascript。但是打字稿给出了以下错误:

No overload matches this call.
  Overload 1 of 3, '(iterable: Iterable<readonly [unknown, unknown]>): Map<unknown, unknown>', gave the following error.
    Argument of type '(number | number[])[]' is not assignable to parameter of type 'Iterable<readonly [unknown, unknown]>'.
      The types returned by '[Symbol.iterator]().next(...)' are incompatible between these types.
        Type 'IteratorResult<number | number[], any>' is not assignable to type 'IteratorResult<readonly [unknown, unknown], any>'.
          Type 'IteratorYieldResult<number | number[]>' is not assignable to type 'IteratorResult<readonly [unknown, unknown], any>'.
            Type 'IteratorYieldResult<number | number[]>' is not assignable to type 'IteratorYieldResult<readonly [unknown, unknown]>'.
              Type 'number | number[]' is not assignable to type 'readonly [unknown, unknown]'.
                Type 'number' is not assignable to type 'readonly [unknown, unknown]'.
  Overload 2 of 3, '(entries?: readonly (readonly [unknown, unknown])[] | null | undefined): Map<unknown, unknown>', gave the following error.
    Argument of type '(number | number[])[]' is not assignable to parameter of type 'readonly (readonly [unknown, unknown])[]'.
      Type 'number | number[]' is not assignable to type 'readonly [unknown, unknown]'.
        Type 'number' is not assignable to type 'readonly [unknown, unknown]'.

似乎编译器将 Map 的签名与 [[1, [2, 3]] 匹配而不是转换后的 [2, 3]

如果 Map 只使用只读数组的后续操作:

  1. 为什么 new Map([[2, 3]]); 是正确的?
  2. 即使 new Map(Object.freeze([[1, [2, 3]]].map(e => e[1]))); 返回 Object.freeze 数组,为什么 readonly 不起作用?

TS 编译器理论上应该能够推导出所有三种形式的等价性。

1 个答案:

答案 0 :(得分:1)

让我们深入研究 MapConstructor 的类型定义

interface MapConstructor {
    new(): Map<any, any>;
    new<K, V>(entries?: readonly (readonly [K, V])[] | null): Map<K, V>;
    readonly prototype: Map<any, any>;
}

根据类型,如果你想提供一个数组作为参数 - 在我们的例子中 entries,这个数组应该是不可变的。

因此,为了使其工作,只需添加 as const

const arr = [[1, [2, 3]]] as const
const x = new Map(arr); // ok

const x = new Map( ([[1, [2, 3]]] as const).map(e => e[1])); // ok
<块引用>

新地图([[2, 3]]);正确吗?

看到这个类型定义:

interface MapConstructor {
    new(): Map<any, any>;
/* -----> */ new<K, V>(entries?: readonly (readonly [K, V])[] | null): Map<K, V>;
    readonly prototype: Map<any, any>;
}

在这里,您处理了泛型,因此 TS 能够推断出参数,即您有一个正好包含两个元素(键和值)的数组。这是最重要的事情。因为这是 Map 的性质,所以你应该有 key 和 value。

请参阅 excess property checks 和下一个示例:

const infer = <K, V>(arg: readonly [K, V]) => arg
const x = infer([1, 2])

如您所见,因为您有文字数组,所以 TS 能够确定您正好有两个值:键和值。

但是,如果您使用引用作为参数(请再次查看多余的属性检查):


const infer = <K, V>(arg: readonly [K, V]) => arg
const tuple = [1,2] // number[]
const x = infer(tuple)

TS 会抱怨,baceuse tuple 被推断为 number[] 并且 TS 无法确定您的数组中有多少个元素。它甚至可以是空数组。这就是为什么 TS 不允许您在此处使用对可变数组的引用。

<块引用>

为什么 new Map(Object.freeze([[1, [2, 3]]]].map(e => e1))) 不起作用?

两者之间存在很大差异:

Object.freeze([[1, [2, 3]]].map(e => e[1]))

([[1, [2, 3]]] as const).map(e => e[1])

在第一种情况下,您可以改变 map 谓词内的数组,但无法改变结果。

在第二种情况下,您无法改变 map 中的数组。

请查看它们的类型签名:

type First = readonly (number | number[])[]
type Second = (readonly [2, 3])[]

let x: First = [1] // is allowed but invalid argument
let y: Second = [1] // does not allowed, because we should have a kay and value. not only key