Как правильно ввести поиск «обработчик» для функции в стиле «диспетчер»

#typescript

#typescript

Вопрос:

Предполагается, что у меня есть такой тип:

 type TInfoGeneric<TType extends string, TValue> = {
  valueType: TType,
  value: TValue, // Correspond to valueType
}
  

Чтобы не повторяться, я создаю карту типов, в которой перечислены возможные, valueType и совпадающие valueType , ну, с типом значения.

 type TInfoTypeMap = {
    num: number;
    str: string;
}
  

Теперь, чтобы действительно создать TInfo , я использую mapped type для отображения всех типов в TInfoGeneric , а затем получаю только его значение.

 type TAllPossibleTInfoMap = {
    [P in keyof TInfoTypeMap]: TInfoGeneric<P, TInfoTypeMap[P]>;
};

type TInfo = TAllPossibleTInfoMap[keyof TAllPossibleTInfoMap]; // TInfoGeneric<"num", number> | TInfoGeneric<"str", string>
  

Затем, чтобы определить обработчики для всех типов, я создаю другой сопоставленный тип только для обработчиков.

 type TInfoHandler = {
    [P in keyof TInfoTypeMap]: (value: TInfoTypeMap[P]) => any
};

const handlers: TInfoHandler = {
    num: (value) => console.log(value.toString(16)),
    str: (value) => console.log(value),
}
  

И, наконец, чтобы действительно использовать обработчик, я создаю функцию, подобную этой:

 function handleInfo(info: TInfo) {
    handlers[info.valueType](info.value); // Error
}
  

Я получил эту ошибку:

 Argument of type 'string | number' is not assignable to parameter of type 'number amp; string'.
  Type 'string' is not assignable to type 'number amp; string'.
    Type 'string' is not assignable to type 'number'.
  

Обычно понятно, что handlers[info.valueType] может быть ((value: number) => any) | ((value: string) => any) . Однако в этом случае:

  • Если info.valueType есть 'num' , то мы можем быть уверены, что handlers[info.valueType] есть (value: number) => any) и info.value есть number . Таким образом, handlers[info.valueType] может вызываться с помощью info.value .
  • Если info.valueType есть 'str' , то мы можем быть уверены, что handlers[info.valueType] есть (value: string) => any) и info.value есть string . Таким образом, handlers[info.valueType] может вызываться с помощью info.value .

Я не уверен, является ли это ограничением Typescript или нет, но возможно ли написать код в этом стиле, чтобы он проверялся на тип?

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

1. Ах, еще один вопрос, связанный с типами записей . Мне действительно нужно открыть новую проблему по этому поводу. Короткий ответ будет заключаться в том, что вы должны использовать утверждение типа и забыть об этом.

2. Хорошо, я только что открыл эту проблему .

Ответ №1:

Да, здесь для вас нет удобного и типобезопасного решения. Я открыл проблему в Microsoft / TypeScript # 30581 по этому поводу, но я не ожидаю, что она будет решена.

Я вижу два основных пути продвижения вперед. Один из них — просто использовать утверждение типа, поскольку вы на законных основаниях знаете здесь больше, чем компилятор. Это может быть так:

 function handleInfo(info: TInfo) {
    // assert your way out.  Not type safe but convenient!
    (handlers[info.valueType] as (x: number | string)=>any)(info.value); 
}
  

Теперь ошибки нет. Это небезопасно для ввода. Но это удобно и не изменяет отправленный JavaScript.


Или вы могли бы попытаться провести компилятор по случаям и доказать ему, что все в порядке. Это сложно, хрупко и имеет последствия во время выполнения:

 const typeGuards: {
  [P in keyof TInfoTypeMap]: (x: TInfoTypeMap[keyof TInfoTypeMap])=>x is TInfoTypeMap[P];
} = {
    num: (x:any): x is number => typeof x === "number",
    str: (x:any): x is string => typeof x === "string"
}

function narrowTInfo<K extends keyof TAllPossibleTInfoMap>(
  x: TInfo, v: K): x is TAllPossibleTInfoMap[K] {
    return typeGuards[v](x.value);
} 

function handleInfo(info: TInfo) {
    if (narrowTInfo(info, "num")) {
        handlers[info.valueType](info.value); // okay
    } else {
        handlers[info.valueType](info.value); // okay
    }
}
  

Это работает, но неприятно. Поэтому я бы рекомендовал утверждение.

Надеюсь, это поможет; удачи!

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

1. Спасибо за ваш ответ! Я тоже собирался сообщить о проблеме, но я действительно не могу объяснить проблему так хорошо, как вы (я не являюсь носителем английского языка). Мне придется использовать метод утверждения, поскольку фактический тип в моей программе — это не просто число и строка, а интерфейсы.