[Перевод] Когда «as never» — единственное, что работает
Эта статья — перевод оригинальной статьи «When 'as never' Is The Only Thing That Works».
Также я веду телеграм канал «Frontend по-флотски», где рассказываю про интересные вещи из мира разработки интерфейсов.
Вступление
as never
, очень редко требуется в TypeScript. Давайте рассмотрим пример, где это необходимо.
Представим, что мы хотим отформатировать некоторый ввод на основе его typeof
. Сначала мы создадим объект formatters
, который сопоставит typeof
с функцией форматирования:
const formatters = {
string: (input: string) => input.toUpperCase(),
number: (input: number) => input.toFixed(2),
boolean: (input: boolean) => (input ? "true" : "false"),
};
Далее мы создадим функцию format
, которая будет принимать на вход string | boolean | number
и форматировать ее в зависимости от ее typeof
.
const format = (input: string | number | boolean) => {
// Здесь нам нужно сделать кастинг, потому что TypeScript не совсем умен.
// достаточно знать, что `typeof input` может быть только
// 'string' | 'number' | 'boolean'
const inputType = typeof input as
| "string"
| "number"
| "boolean";
const formatter = formatters[inputType];
return formatter(input);
/*
Argument of type 'string | number | boolean' is not assignable to parameter of type 'never'.
Type 'string' is not assignable to type 'never'.
*/
};
Но возникает странная ошибка:
Type 'string' is not assignable to type 'never'.
Что здесь происходит?
Объединения функций с несовместимыми параметрами
Давайте подробнее рассмотрим тип formatter
внутри нашей функции format
:
const format = (input: string | number | boolean) => {
const inputType = typeof input as
| "string"
| "number"
| "boolean";
const formatter = formatters[inputType]; // ((input: string) => string) | ((input: number) => string) | ((input: boolean) => "true" | "false")
return formatter(input);
};
Как вы видите, она представляет собой объединение функций, каждая из которых имеет свой параметр. Одна функция принимает строку, другая — число, а последняя — булево.
Как мы можем вызвать эту функцию со строкой и числом одновременно? Никак. Поэтому функция на самом деле разрешается так:
type Func = (input: never) => string;
Разве параметры не должны привести к объединению?
Вы можете подумать: «Разве параметры не должны преобразовываться в объединение string | number | boolean
?».
Это не работает, потому что вызов formatters.string
с числом небезопасен. Вызов formatters.boolean
с числом небезопасен.
Таким образом, never
— единственный тип, который имеет смысл.
Как это исправить?
Мы знаем, что логика этой функции верна. Мы знаем, что formatters[inputType]
преобразуется в правильный тип.
Поэтому мы можем использовать as never
:
const format = (input: string | number | boolean) => {
const inputType = typeof input as
| "string"
| "number"
| "boolean";
const formatter = formatters[inputType];
return formatter(input as never);
};
Это заставляет TypeScript рассматривать input
как тип never
— который, конечно же, можно присвоить параметру форматера never.
Не будет ли работать as any?
Удивительно, но здесь any
не работает:
const format = (input: string | number | boolean) => {
const inputType = typeof input as
| "string"
| "number"
| "boolean";
const formatter = formatters[inputType];
return formatter(input as any);
// Argument of type 'any' is not assignable to parameter of type 'never'.
};
Это приводит к чудовищной ошибке:
Argument of type 'any' is not assignable to parameter of type 'never'
Так что, as never
это единственный выход.