#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
inValue extends string | number
. Есть ли какой-либо способ, который можно было бы вывести из типов значений карт автоматически?2. @BrainCore Я сделал обновление