Типографский текст: Общий тип значения по карте

#typescript #type-inference #typescript-generics

Вопрос:

Вот упрощенная версия проблемы. Я пытаюсь создать функцию, которая будет работать в общем виде на интерфейсе со 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 типом значения.

Ответ №1:

Как насчет использования объектов вместо карт ?

 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);
 

игровая площадка

Комментарии:

1. Отлично! Я использую Typescript 4.1, где это не работает, но благодаря вашей ссылке на игровую площадку я вижу, что это будет работать на TS >= 4.3.

Ответ №2:

Вы можете использовать некоторые условные типы для определения типов ключей и значений ваших карт. Вы столкнетесь с проблемой типа при вызове set , потому что TS выводит d[dataKey] как a Map<string, number> | Map<string, string> . Но мы знаем, что мы здесь делаем, поэтому мы можем безопасно добавить @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.
 

Вот также ссылка на игровую площадку.

Ответ №3:

обновленный

Это можно сделать с помощью умозаключения. Вы должны вывести всю структуру данных.

 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 — делает выводы map1 и map2

HashMap — выводит всю структуру данных (аргумент)

Игровая площадка

Если вас интересует вывод аргументов функции, вы можете ознакомиться с моей статьей

Комментарии:

1. Спасибо за это предложение. Это не идеально из-за явного переопределения string | number in Value extends string | number . Есть ли какой-либо способ, который можно было бы вывести из типов значений карт автоматически?

2. @BrainCore Я сделал обновление