【问题标题】:Typescript: Generic over Map<K,V> value type打字稿:通用 Map<K,V> 值类型
【发布时间】:2021-12-03 07:31:00
【问题描述】:

这是问题的简化版本。我正在尝试创建一个函数,该函数将在具有Map 属性的接口上进行一般操作。

interface Data {
  map1: Map<string, number>;
  map2: Map<string, string>;
}

const d: Data = {
  map1: new Map(),
  map2: new Map(),
};

// This doesn't work
public dataSet<T extends keyof Data, V extends Data[T]... (need help here)>(d: data, dataKey: T, key: string, val: V) {
  d[dataKey].set(key, val);
}

// Ideally, called like:
dataSet(d, 'map1', 'alpha', 3);
dataSet(d, 'map2', 'beta', 'charlie');

V的类型定义目前是Map类型而不是Map的值类型。

【问题讨论】:

    标签: typescript type-inference typescript-generics


    【解决方案1】:

    更新

    可以借助推理来完成。您应该推断出整个数据结构。

    interface Dictionary {
      map1: Map<string, number>;
      map2: Map<string, string>;
    }
    
    const d: Dictionary = {
      map1: new Map(),
      map2: new Map(),
    };
    
    const dataSet = <
      MapKey,
      MapValue,
      HashMapKey extends string,
      HashMap extends Record<HashMapKey, Map<MapKey, MapValue>>,
      >(hashMap: HashMap, dataKey: HashMapKey, key: MapKey, val: MapValue) =>
      hashMap[dataKey].set(key, val);
    
    // Ideally, called like:
    dataSet(d, 'map1', 'alpha', 2) // ok
    dataSet(d, 'map1', 'alpha', 'str') // expected error
    
    dataSet(d, 'map2', 'beta', 'charlie');
    dataSet(d, 'map2', 'beta', 4); // expected error
    

    HashMapKey - 推断出map1map2

    HashMap - 推断整个数据结构(参数)

    Playground

    如果你对函数参数推断感兴趣,可以查看我的article

    【讨论】:

    • 感谢您的建议。由于string | numberValue extends string | number 中的显式重新定义,这并不理想。有什么方法可以自动从地图的值类型中推断出来?
    • @BrainCore 我更新了
    【解决方案2】:

    您可以使用一些条件类型来推断 Map 的键和值类型。调用set 时会遇到类型问题,因为TS 将d[dataKey] 推断为Map&lt;string, number&gt; | Map&lt;string, string&gt;。但我们知道我们在这里做什么,所以我们可以安全地添加@ts-expect-error 行。

    我在操场上的 TS v4.1.5 上对此进行了测试。

    interface Data {
      map1: Map<string, number>;
      map2: Map<string, string>;
    }
    
    const d: Data = {
      map1: new Map(),
      map2: new Map(),
    };
    
    type GetKey<M extends Map<any, any>> = M extends Map<infer K, any> ? K : never;
    type GetValue<M extends Map<any, any>> = M extends Map<any, infer V> ? V : never;
    
    function dataSet<K extends keyof Data>(d: Data, dataKey: K, key: GetKey<Data[K]>, val: GetValue<Data[K]>) {
      // @ts-expect-error
      d[dataKey].set(key, val);
    }
    
    // This now enforces the types correctly.
    dataSet(d, 'map1', 'alpha', 3);
    dataSet(d, 'map2', 'beta', 'charlie');
    dataSet(d, 'map2', 'foo', true); // Errors as expected.
    

    这里也是link to the playground

    【讨论】:

      【解决方案3】:

      使用对象而不是地图怎么样?

      interface Data {
        map1: {
          [K: string]: number
        };
        map2: {
          [K: string]: string
        };
      }
      
      const d: Data = {
        map1: {},
        map2: {},
      };
      
      function dataSet<T extends keyof Data, V extends Data[T][string]>(d: Data, dataKey: T, key: string, val: V) {
        d[dataKey][key] = val;
      }
      
      
      // works:
      dataSet(d, 'map1', 'alpha', 3);
      dataSet(d, 'map2', 'beta', 'charlie');
      
      // Errors
      dataSet(d, 'map1', 'alpha', 'charlice');
      dataSet(d, 'map2', 'beta', 3);
      

      playground

      【讨论】:

      • 太棒了!我正在使用 Typescript 4.1 这不起作用,但感谢你的游乐场链接,我看到这将适用于 TS >= 4.3。
      猜你喜欢
      • 2019-10-10
      • 1970-01-01
      • 2021-10-25
      • 1970-01-01
      • 2011-04-21
      • 2018-06-08
      • 1970-01-01
      • 1970-01-01
      • 2020-06-15
      相关资源
      最近更新 更多