Несовпадение типов typescript
Собственно, проблема в коде ниже. Не пойму, почему в первом случае возникает такая ошибка. При том, что во втором все нормально.
interface DataMap {
key1: number[];
key2: string[];
};
type DataHandler<K extends keyof DataMap> = {(value: DataMap[K]): void};
type Data<K extends keyof DataMap> = [K, DataHandler<K>];
const storage1: Data<keyof DataMap>[] = [];
const storage2: {[K in keyof DataMap]: DataHandler<K>[]} = {
key1: [],
key2: [],
};
function getData<K extends keyof DataMap>(key: K, handler: DataHandler<K>): void {
storage1.push([key, handler]); // Type 'DataHandler<K>' is not assignable to type 'DataHandler<keyof DataMap>'. Type 'keyof DataMap' is not assignable to type 'K'.
storage2[key].push(handler); // OK
}
Ответы (1 шт):
Проблема тут в следующем.
Если раскрыть все эти keyof и прочее, то у вас получается следующая картина:
type DataHandlerNumber = { (value: number): void };
type DataHandlerString = { (value: string): void };
type DataHandlerAll = { (value: number | string): void };
type Storage1Type = [ 'key1' | 'key2', DataHandlerAll ][];
type Storage2Type = {
key1: DataHandlerNumber[],
key2: DataHandlerString[],
};
Переменная storage2 хранит все обработчики отдельно, и с ней никаких проблем не возникает. А вот переменная storage1, судя по использованию, пытается хранить нечто вроде полиморфного типа, однако на самом деле хранит что-то другое.
В том виде, в котором сейчас вы написали типы, у Typescript нет информации что первый и второй элементы кортежа Data связаны друг с другом.
Посмотрим как ваш тип Data объявлен ещё раз, опустив неважные детали:
type Data<K> = [K, DataHandler<K>];
// Data<'key1' | 'key2'> = [ 'key1' | 'key2', DataHandler<'key1' | 'key2'> ]
Дженерики в Typescript не являются дистрибутивными относительно оператора |, и когда вы пишете Data<'key1' | 'key2'> - получается совершенно не то же самое, что Data<'key1'> | Data<'key2'>.
Как можно это исправить? Ну, например можно использовать условный оператор, поскольку он по построению дистрибутивен. Замечу, что условие в этом операторе всегда истинное, сам оператор нужен не ради условия, а ради дистрибутивности:
type Data<K> = K extends any ? [K, DataHandler<K>] : never;
// Data<'key1' | 'key2'> = [ 'key1', DataHandler<'key1'> ] | [ 'key2', DataHandler<'key2'> ];
Теперь структура данных выглядит именно так, как вам и нужно. Но есть следующая проблема: вы не можете воспользоваться ей в обобщённом коде. Смотрите:
function getData<K extends keyof DataMap>(key: K, handler: DataHandler<K>): void {
storage1.push([key as K, handler]);
// Type 'DataHandler<K>' is not assignable to type 'DataHandler<"key1"> | DataHandler<"key2">'
}
Проблема тут в том, что функцию getData в теории можно вызвать вот так: getData<'key1' | 'key2'>('key1', …);, и в итоге тип выражения [key as K, handler] окажется недопустимым.
К сожалению, у этой проблемы нет нормального решения. Система типов Typescript - это всё же больше набор трюков для типизации библиотек, а не система формальной верификации кода. Поэтому наилучшим из известных мне способов является просто отказ от типизации конкретно в этом месте:
function getData<K extends keyof DataMap>(key: K, handler: DataHandler<K>): void {
storage1.push([key, handler as any]);
storage2[key].push(handler);
}