Когда «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 это единственный выход.


ссылка на оригинал статьи https://habr.com/ru/articles/791822/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *