Эта статья — перевод оригинальной статьи «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 это единственный выход.
