Введение: почему TypeScript кусает новичков, или как не провалить первый проект
Вы когда-нибудь чувствовали, что TypeScript — это как строгий учитель, который ставит двойки за каждую мелочь? Если да — добро пожаловать в клуб. Каждый второй новичок, переходя с JavaScript на TypeScript, совершает одни и те же ошибки. И это нормально: TypeScript действительно сложнее, он требует дисциплины и понимания типов.
Но есть и хорошая новость: большинство ошибок при изучении TypeScript можно предсказать и предотвратить. В этой статье я собрал 7 самых частых граблей, на которые наступают начинающие разработчики. Мы разберем каждую ошибку с примерами кода, объясним, почему это плохо, и покажем, как сделать правильно.
Если вы только начали изучать TypeScript или уже набили пару шишек — эта статья сэкономит вам часы дебаггинга и нервов. Поехали!
Ошибка №1: Бесконтрольное использование any (источник всех бед)
Что делают новички
Самая распространенная ошибка среди тех, кто изучает TypeScript — писать везде any. Кажется: «Ну подумаешь, тип не указал, код же работает!».
// ❌ Плохо: any убивает всю пользу TypeScript
function processData(data: any) {
console.log(data.name.toUpperCase());
return data.price * 2;
}
const result = processData({ name: "ноутбук", price: 500 });
// Всё работает... пока не передашь null
processData(null); // Ошибка в рантайме!
Почему это ошибка
any — это чёрная дыра TypeScript. Когда вы пишете any, вы говорите компилятору: «Отойди, я сам разберусь». И он отходит. Вы теряете автодополнение, проверку типов и защиту от глупых ошибок. TypeScript превращается обратно в JavaScript — со всеми его недостатками.
Как исправить
Используйте конкретные типы. Если тип неизвестен заранее — unknown ваш друг. Он заставит вас проверить данные перед использованием.
// ✅ Хорошо: конкретные типы и unknown
function processData(data: { name: string; price: number }) {
console.log(data.name.toUpperCase());
return data.price * 2;
}
// Или если тип правда неизвестен:
function safeProcess(data: unknown) {
if (typeof data === 'object' && data !== null && 'name' in data) {
// Теперь TypeScript знает, что data — объект с полем name
console.log((data as { name: string }).name);
}
}
Совет: Включите правило no-explicit-any в ESLint — оно не даст вам использовать any без веской причины.
Ошибка №2: Игнорирование strict mode (или «почему у меня всё работает, но не так»)
Что делают новички
Многие начинающие разработчики при изучении TypeScript отключают строгий режим в tsconfig.json или не включают его вовсе. Дескать, «и так сойдёт, меньше ошибок компиляции».
// ❌ Плохо: strict: false
// tsconfig.json
{
"compilerOptions": {
"strict": false, // Ошибка!
"noImplicitAny": false,
"strictNullChecks": false
}
}
// Теперь этот код скомпилируется без ошибок:
function greet(name: string | null) {
console.log(name.toUpperCase()); // Потенциальный null!
}
greet(null); // Runtime error: Cannot read properties of null
Почему это ошибка
Без строгого режима TypeScript теряет половину своей силы. Вы не получаете предупреждений о потенциальных null и undefined, не проверяете типы аргументов — и в итоге ловите баги в продакшене.
Как исправить
Всегда включайте strict: true. Это активирует все строгие проверки разом.
// ✅ Хорошо: strict: true
{
"compilerOptions": {
"strict": true,
"target": "ES2020",
"module": "ESNext"
}
}
// Теперь TypeScript заставит вас обработать null:
function greet(name: string | null) {
if (name === null) {
console.log("Привет, аноним!");
return;
}
console.log(name.toUpperCase()); // Безопасно
}
Ошибка №3: Путаница между type и interface (и когда что использовать)
Что делают новички
Новички часто не понимают разницы между type и interface и используют их как попало. Или, наоборот, боятся использовать что-то одно, потому что «вдруг неправильно».
// ❌ Плохо: хаотичное использование
type User = {
name: string;
age: number;
}
interface UserInterface {
name: string;
age: number;
}
// Потом пытаются расширить type через extends:
type Admin = User & { role: string }; // Работает, но неочевидно
// А interface можно расширить красиво:
interface AdminInterface extends UserInterface {
role: string;
}
Почему это ошибка
Хотя type и interface во многом взаимозаменяемы, у них есть важные различия. interface можно объединять (declaration merging), он лучше подходит для описания объектов и классов. type — для объединений, пересечений и примитивных типов. Использование не по назначению запутывает код.
Как исправить
Простое правило: используйте interface для описания объектов/классов, type — для всего остального (union, intersection, примитивы).
// ✅ Хорошо: чёткое разделение
// Interface для объектов
interface Product {
id: number;
name: string;
price: number;
}
// Type для объединений
type Status = 'active' | 'inactive' | 'pending';
type Result<T> = { success: true; data: T } | { success: false; error: string };
// Type для функций
type Callback = (value: string) => void;
Ошибка №4: Неправильная работа с union и intersection типами
Что делают новички
При изучении TypeScript новички часто неправильно сужают (narrow) объединённые типы. Они пытаются обратиться к свойству, которого может не существовать, или используют неправильные проверки.
// ❌ Плохо: неправильное сужение типа
type Shape =
| { kind: 'circle'; radius: number }
| { kind: 'square'; side: number }
| { kind: 'triangle'; base: number; height: number };
function getArea(shape: Shape) {
// Ошибка! radius может не быть у square или triangle
return Math.PI * shape.radius ** 2;
}
// Или слишком общая проверка:
function getArea2(shape: Shape) {
if ('radius' in shape) { // Работает, но ненадёжно
return Math.PI * shape.radius ** 2;
}
// ...
}