Тип never в TypeScript: как исправить ошибку "not assignable to parameter"

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Введение: почему TypeScript кричит на пустой массив?

Вы когда-нибудь писали код на TypeScript, запускали компиляцию, а в ответ получали загадочное сообщение: "Argument of type 'X' is not assignable to parameter of type 'never'"? Если да — вы не одиноки. Эта ошибка — настоящий камень преткновения для разработчиков всех уровней. На Stack Overflow этот вопрос собрал тысячи просмотров, и сегодня мы разберем его раз и навсегда.

Представьте ситуацию: вы объявляете пустой массив, пытаетесь в него что-то добавить, а TypeScript блокирует операцию. Знакомо? Давайте разберемся, почему так происходит и, главное, как это исправить.

В этой статье мы не просто переведем сухую документацию. Мы пройдем путь от появления ошибки до её полного устранения с реальными примерами, которые вы сможете сразу применить в своих проектах.

Что такое тип never в TypeScript?

Прежде чем нырять в дебри ошибок, давайте разберемся с самим типом never. В TypeScript этот тип обозначает "значение, которого не может быть". Звучит философски, но на практике всё просто:

  • never — это тип для функций, которые никогда не завершаются (бесконечный цикл) или всегда выбрасывают исключение.
  • never — это "дно" системы типов. Вы можете присвоить never чему угодно, но ничто не может быть присвоено never (кроме самого never).

Теперь представьте, что TypeScript видит пустой массив и не знает, какие элементы в нём будут. В режиме строгой типизации (strict: true в tsconfig.json) компилятор делает логичный вывод: "Если массив пуст и тип не указан, значит, в нём ничего не может быть — тип элементов — never".

// Пример: TypeScript выводит тип как never[]
const emptyArray = [];
emptyArray.push("hello"); // Ошибка!
// Argument of type 'string' is not assignable to parameter of type 'never'

Вот она, та самая ошибка. TypeScript честно предупреждает: "Ты сказал, что массив может содержать только never, а пытаешься добавить строку. Так не пойдёт!"

Основные сценарии возникновения ошибки

1. Пустой массив без аннотации типа

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

// ❌ Проблема
const items = []; // TypeScript выводит: any[] (если strict: false) или never[] (если strict: true)
items.push({ id: 1, name: "Alice" }); // Ошибка при strict: true

// ✅ Решение: явно укажите тип
const items: Array<{ id: number; name: string }> = [];
items.push({ id: 1, name: "Alice" }); // Работает!

2. Методы массивов, возвращающие never (filter, reduce)

Метод filter с неправильным предикатом или reduce без начального значения могут привести к выведению типа never.

// ❌ Проблема с filter
const numbers: (number | null)[] = [1, null, 2, null, 3];
const onlyNumbers = numbers.filter(item => item !== null); 
// TypeScript может вывести тип как (number | null)[] или даже never[], если предикат "сломан"

// ✅ Решение: используйте type guard
const onlyNumbersSafe: number[] = numbers.filter((item): item is number => item !== null); 

// ❌ Проблема с reduce
const result = [1, 2, 3].reduce((acc, val) => acc + val); 
// Ошибка! TypeScript не знает начальный тип аккумулятора

// ✅ Решение: укажите начальное значение
const resultSafe = [1, 2, 3].reduce((acc, val) => acc + val, 0); // Работает!

3. Дженерики и условные типы

Более сложный случай — когда TypeScript не может вывести тип из-за сложных обобщений.

// ❌ Проблема
function getFirstElement<T>(arr: T[]): T | undefined {
  return arr[0];
}

function processItems<T>(items: T[]): void {
  // Какой-то код
}

// Если T выводится как never, получим ошибку
const data = getFirstElement([]); // data имеет тип undefined (но массив never[])
processItems(data ? [data] : []); // Может вызвать ошибку

// ✅ Решение: явно параметризуйте дженерик
const data2 = getFirstElement<string>([]); // data2: string | undefined

Как TypeScript выводит тип never?

Чтобы лучше понять природу ошибки, заглянем "под капот" TypeScript. Компилятор использует алгоритм вывода типов, который в некоторых ситуациях заходит в тупик.

Основные правила:

  • Если массив объявлен пустым и без аннотации типа, TypeScript присваивает ему тип never[] (при strict: true).
  • Если функция возвращает never (например, всегда выбрасывает исключение), результат нельзя присвоить другой переменной.
  • Если в объединении типов (union type) все варианты исчерпаны, остается только never.
// Пример с объединением типов
type Animal = "dog" | "cat";

function handleAnimal(animal: Animal) {
  switch(animal) {
    case "dog":
      return "Гав!";
    case "cat":
      return "Мяу!";
    default:
      // Здесь animal имеет тип never — все варианты обработаны
      const _exhaustiveCheck: never = animal;
      return _exhaustiveCheck;
  }
}

Практические решения: пошаговое руководство

Шаг 1. Явная аннотация типа

Самый простой и надёжный способ — всегда указывать тип для пустых массивов и переменных, которые будут заполнены позже.

// Для массивов
const users: User[] = [];
const ids: number[] = [];
const mixed: (string | number)[] = [];

// Для других типов
let data: Record<string, unknown> = {};
let callback: () => void = () => {};

Шаг 2. Использование type assertion (с осторожностью)

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

const items = [] as Array<{ id: number }>;
items.push({ id: 1 }); // Работает

// Но будьте осторожны — это отключает проверку типов!
const risky = [] as any[]; // Лучше избегать

Шаг 3. Исправление сложных случаев с дженериками

Если ошибка возникает в функциях с обобщёнными типами, проверьте:

  • Все ли дженери

Блоги

Рекомендаций по книгам