【问题标题】:How to use `(K | V)[][]` as `ReadonlyArray<[K, V]>` in a Map?如何在 Map 中使用 `(K | V)[][]` 作为 `ReadonlyArray<[K, V]>`?
【发布时间】:2021-06-17 04:44:44
【问题描述】:

我想在 Map 构造函数中使用(K | V)[][] 作为ReadonlyArray&lt;[K, V]&gt;。假设我们的 V 是一个接口IItem,K 是一个基本的number

interface IItem {
    key: number;
    val: string;
}

const items = [[1, {key: 1, val: "a"}]] 
const ze_map = new Map<number, IItem>(items);
console.log(JSON.stringify([...ze_map]));

默认情况下,typescript 会将项目的类型视为: (number | IItem)[][]。这将不起作用并引发错误:

'(number | { key: number; val: string; })[][]' 类型的参数不可分配给'ReadonlyArray' 类型的参数。
类型 '(number | { key: number; val: string; })[]' 缺少来自类型 '[{}, {}]' 的以下属性:0, 1

幸运的是,我们可以强制类型为 Array&lt;[number, IItem]&gt; 以取悦 Map

const items2 : Array<[number, IItem]> = [[1, {key: 1, val: "a"}]] 
const ze_map2 = new Map<number, IItem>(items2);
console.log(JSON.stringify([...ze_map]));

这项工作符合预期。好吧,让我们转移到我的问题。如果我们不能强制类型怎么办?

const arr = [
    { key: 3, val: 'bar' },
    { key: 5, val: 'world' }
];
const result: Array<[number, IItem]> = arr.map(i => ([i.key, i]));

const ze_map3 = new Map(result);
console.log(JSON.stringify([...ze_map]));

这不起作用,因为这里的(number | { key: number; val: string; })[][] 不是Array&lt;[number, IItem]&gt;。那么,如何在 Map 中使用(K | V)[][] 作为ReadonlyArray&lt;[K, V]&gt;

你可以尝试所有代码online

我阅读了How to define Map with correlation between a key type and a value type, while they both are unionsTypescript Map<enum, set<enum>> "No overload matches this call", but I don't get why? 都没有找到解决方案。我可能错过了解决方案。

我还阅读了MDN entry on Map,它说它应该可以按预期工作,如果您在 vanilla JS 中尝试,它确实可以按预期工作:

var arr = [
    { key: 3, val: 'bar' },
    { key: 5, val: 'world' }
];

var result = arr.map(i => [i.key, i]);
const ze_map = new Map(result);

console.log(JSON.stringify([...ze_map])); 

Try it online!

【问题讨论】:

    标签: javascript typescript dictionary


    【解决方案1】:

    问题在于.map 回调的主体。每当函数返回一个数组时,Typescript 会将返回类型解释为普通数组类型,而不是 tuple type

    避免as

    as 断言会起作用,但它可能很危险,因为它告诉 Typescript“将此类型视为 [number, IItem],即使它确实不是”,而不是告诉 Typescript“我希望这种类型是 [number, IItem]所以请确保它是。”在您的情况下,不需要as,因为它确实是类型[number, IItem]。我们只需要让 typescript 以这种方式解释它。

    设置一个泛型

    最简单的方法是在.map 函数上设置泛型类型参数。这里的泛型变量设置回调的返回类型,所以我们设置为[number, IItem]

    const result = arr.map<[number, IItem]>(i => [i.key, i]); // result has type [number, IItem][]
    
    const ze_map = new Map(result); // no errors :)
    

    外部化回调

    另一种方法是将您的地图回调i =&gt; [i.key, i] 定义为自己的函数,以便您可以注释返回类型。

    const toTuple = (i: IItem): [number, IItem] => [i.key, i];
    
    const result = arr.map(toTuple); // result has type [number, IItem][]
    
    const ze_map = new Map(result); // no errors :)
    

    Typescript Playground Link

    【讨论】:

      【解决方案2】:

      我觉得可能有更好的方法来实现这一点,但您可以通过在 map 例程中强制转换来强制类型:

      const arr = [
          { key: 3, val: 'bar' },
          { key: 5, val: 'world' }
      ];
      const result = arr.map(i => ([i.key, i] as [number, IItem])); // here come the trick
      const ze_map3 = new Map(result);
      console.log(JSON.stringify([...ze_map3]));
      

      as 关键字是一个类型断言,它告诉 tsc 将对象视为另一种类型(即我们的[K, V]),而不是编译器推断对象的类型(即(K | V)[])。

      好消息是as [K, V] 在 js 输出中是完全透明的:

      "use strict";
      const arr = [
          { key: 3, val: 'bar' },
          { key: 5, val: 'world' }
      ];
      const result = arr.map(i => [i.key, i]);
      const ze_map3 = new Map(result);
      console.log(JSON.stringify([...ze_map3]));
      

      Try it Online

      source

      【讨论】:

        猜你喜欢
        • 2019-10-10
        • 2011-04-21
        • 2018-11-23
        • 1970-01-01
        • 2011-03-06
        • 2019-01-23
        • 2014-05-09
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多