Ошибки TypeScript сначала выглядят как язык юридических документов: Type 'string' is not assignable to type 'number', Object is possibly 'undefined', Property does not exist, Type 'unknown' is not assignable. Хочется быстро поставить any, выключить strict и жить дальше
Но почти каждая такая ошибка отвечает на простой вопрос: что TypeScript ожидал и что получил. Если научиться читать это спокойно, TypeScript перестает быть шумом и становится ранним предупреждением
В этом уроке разберем четыре частые темы: any, unknown, never и строгий режим
- Как читать ошибку TypeScript
- any: быстро, но опасно
- unknown: безопасное "не знаю"
- Narrowing: сужение типа
- strict null checks
- never: невозможное состояние
- as: когда утверждение типа оправдано
- strict: почему ошибок стало больше
- Порядок исправления ошибки
- Частые ошибки
- Мини-задание
- Что может быть еще интересно по этой теме
- any всегда плохой?
- unknown лучше any?
- Зачем нужен never новичку?
- Нужно ли всегда включать strict?
- Что почитать дальше по TypeScript
Как читать ошибку TypeScript
Начинай не с копирования всей ошибки в поиск, а с трех вопросов:
- Какой тип ожидался?
- Какой тип пришел?
- Где TypeScript потерял уверенность?
Пример:
function formatMinutes(minutes: number): string {
return `${minutes} минут`;
}
formatMinutes("25");
Смысл ошибки: функция ожидает number, а получает string
Исправление не в том, чтобы обмануть TypeScript:
formatMinutes("25" as any);
А в том, чтобы передать правильный тип:
formatMinutes(25);
Или явно преобразовать данные, если они пришли строкой:
formatMinutes(Number("25"));
any: быстро, но опасно
any говорит TypeScript: "не проверяй это значение"
let value: any = "TypeScript";
value.toFixed(2);
TypeScript не ругнется. Но во время выполнения будет ошибка, потому что у строки нет toFixed
any заразен. Если значение стало any, дальше из него легко получить другие any, и проверка разваливается:
const response: any = {};
const title = response.data.lesson.title;
TypeScript молчит, но код может упасть на первом же undefined
Иногда any нужен: миграция старого проекта, временный мост к библиотеке без типов, быстрый черновик. Но в учебном коде лучше не делать его привычкой
unknown: безопасное "не знаю"
unknown тоже означает неизвестный тип, но ведет себя безопаснее:
let value: unknown = "TypeScript";
value.toUpperCase();
TypeScript не разрешит вызвать метод, пока мы не проверим тип:
if (typeof value === "string") {
console.log(value.toUpperCase());
}
Для данных извне unknown честнее, чем any. Например, мы получили JSON и пока не знаем его форму:
const data: unknown = await response.json();
Дальше нужно проверить, что это объект с нужными полями. TypeScript заставляет нас не притворяться, что чужие данные уже правильные
Narrowing: сужение типа
Narrowing — это когда TypeScript после проверки понимает тип точнее
function printId(id: number | string): string {
if (typeof id === "number") {
return id.toString();
}
return id.trim();
}
До if тип id — number | string. Внутри первого блока это number, после него — string
Narrowing часто решает ошибки без as и без any
Еще пример:
type Lesson = {
title: string;
description?: string;
};
function printDescription(lesson: Lesson): string {
if (!lesson.description) {
return "Описание не добавлено";
}
return lesson.description.toUpperCase();
}
Проверка if (!lesson.description) помогает TypeScript понять, что ниже description уже есть
strict null checks
В строгом режиме TypeScript внимательнее относится к null и undefined
Пример:
type User = {
name: string;
};
let user: User | null = null;
console.log(user.name);
TypeScript ругнется: user может быть null
Правильно:
if (user) {
console.log(user.name);
}
Или:
function printUser(user: User | null): string {
if (!user) {
return "Пользователь не найден";
}
return user.name;
}
Ошибка неприятная только первые несколько раз. Потом она начинает экономить время, потому что Cannot read properties of null в браузере становится меньше
never: невозможное состояние
never означает значение, которого не должно быть
Самый полезный пример — проверка полноты switch:
type Status = "draft" | "published" | "archived";
function getStatusLabel(status: Status): string {
switch (status) {
case "draft":
return "Черновик";
case "published":
return "Опубликовано";
case "archived":
return "Архив";
default:
const neverStatus: never = status;
return neverStatus;
}
}
Пока все статусы обработаны, в default ничего не остается, и status становится never
Теперь добавим новый статус:
type Status = "draft" | "published" | "archived" | "review";
Если забыть добавить case "review", TypeScript подсветит строку с neverStatus. Это хороший способ не забыть новые варианты
as: когда утверждение типа оправдано
Иногда ты знаешь больше, чем TypeScript:
const input = document.querySelector("#email") as HTMLInputElement | null;
Но as — не проверка. Это утверждение. Если ты ошибся, TypeScript поверит, а код может упасть
Поэтому после as все равно часто нужна проверка:
const input = document.querySelector("#email") as HTMLInputElement | null;
if (!input) {
throw new Error("Email input not found");
}
console.log(input.value);
Не используй as как молоток для каждой ошибки. Сначала попробуй narrowing, правильный тип функции или корректную форму объекта
strict: почему ошибок стало больше
strict: true включает несколько строгих проверок. Поэтому после включения strict старый проект может покраснеть
Это не значит, что strict плохой. Это значит, что раньше часть рисков была невидимой:
- значение может быть
undefined; - параметр получил неявный
any; - функция возвращает не то;
- объект не проверен перед использованием.
Для нового проекта strict лучше включать сразу. Для старого — включать постепенно, если проект большой
Порядок исправления ошибки
Я бы шел так:
- Прочитать ожидаемый и фактический тип.
- Найти место, где тип потерялся.
- Добавить явный тип к функции, объекту или состоянию.
- Если значение приходит извне, проверить его.
- Использовать
unknownвместоany, если форма неизвестна. - Использовать
asтолько когда действительно понятно, почему TypeScript не может вывести тип сам.
Частые ошибки
Первая ошибка — ставить any вместо понимания причины
Вторая ошибка — выключать strict из-за первых десяти ошибок. Лучше исправить одну категорию ошибок и идти дальше
Третья ошибка — использовать unknown, но не делать проверку. unknown полезен только вместе с narrowing
Четвертая ошибка — писать as SomeType для данных API без runtime-проверки. TypeScript поверит, но сервер от этого не станет честнее
Мини-задание
Возьми код:
type ApiLesson = {
title: string;
minutes: number;
};
const data: unknown = {
title: "Ошибки TypeScript",
minutes: "30",
};
Напиши функцию проверки:
function isApiLesson(value: unknown): value is ApiLesson {
if (typeof value !== "object" || value === null) {
return false;
}
const lesson = value as Record<string, unknown>;
return (
typeof lesson.title === "string" &&
typeof lesson.minutes === "number"
);
}
Потом проверь:
if (isApiLesson(data)) {
console.log(data.title);
} else {
console.log("Данные урока пришли в неверном формате");
}
В этом примере minutes строка, поэтому проверка должна вернуть false
Что может быть еще интересно по этой теме
any всегда плохой?
Нет. Он бывает нужен при миграции или работе с неизвестной библиотекой. Но если any становится обычным способом решать ошибки, TypeScript теряет смысл
unknown лучше any?
Для данных неизвестной формы — да. unknown заставляет проверить тип перед использованием
Зачем нужен never новичку?
В первую очередь для проверки невозможных веток, особенно в switch по union types
Нужно ли всегда включать strict?
Для нового проекта — да, я бы включал. Для старого проекта можно включать постепенно, чтобы не остановить всю разработку
Что почитать дальше по TypeScript
- Union, literal types и enum в TypeScript — там
neverособенно полезен для статусов. - tsconfig.json простыми словами: strict, target и module — чтобы понимать, что именно включает strict.
- TypeScript в Node.js: первый Express API — пример, где входные данные из HTTP требуют осторожности.
- Generics в TypeScript: первый пример без головоломок — чтобы не заменять переиспользуемые типы на
any.



