TypeScript для новичков: 7 фатальных ошибок и как их избежать

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

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

Начать курс

Введение: почему 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;
    }
    // ...
}

Почему это

Блоги

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