Ты собрал «Змейку» промптами, всё работало, а потом добавил таблицу рекордов — и счёт вместо 120 стал показывать "12045". Ошибка нашлась через час, хотя её причина умещается в одну строку: где-то число случайно склеилось со строкой. Такие поломки ловятся не отладкой, а типами — заранее, прямо в редакторе. Дальше разберём, что такое TypeScript, чем он отличается от привычного JavaScript и почему типы стали ещё нужнее после того, как код за тебя начал писать ИИ.
Содержание
- Что такое TypeScript простыми словами
- Чем TypeScript отличается от JavaScript и зачем нужна сборка
- Что такое тип и какие бывают базовые типы
- Типы объектов, интерфейсы и объединения на примере рекордов «Змейки»
- Зачем типы нужны, когда код пишет ИИ
- Как включить TypeScript в проект
- TypeScript и React: типизация пропсов и состояния
- Что делать с ошибками типов и почему
any— плохая привычка - Когда TypeScript не нужен
- Частые вопросы
- Заключение
- Источники
Что такое TypeScript простыми словами
TypeScript — это язык программирования, который построен поверх JavaScript. Он берёт обычный JavaScript и добавляет к нему одну вещь: возможность заранее сказать, какого рода данные лежат в каждой переменной, что принимает функция и что она возвращает. Всё остальное — синтаксис, библиотеки, привычные конструкции — остаётся тем же самым.
Самое короткое определение звучит так: TypeScript — это JavaScript с типами. Любой рабочий файл на JavaScript уже является корректным кодом на TypeScript. Ты не учишь новый язык с нуля, ты добавляешь к знакомому языку описания данных.
Зачем это нужно, проще всего показать на бытовой аналогии. У тебя дома есть банки для круп: на одной написано «гречка», на другой «сахар», на третьей «соль». Подписи ничего не меняют физически — банка примет что угодно. Но когда ты варишь кашу и тянешься к банке с подписью «гречка», ты не насыпаешь туда соль по ошибке, а если насыпал, то заметишь несоответствие сразу, а не за ужином. Типы — это подписи на банках внутри кода.
В JavaScript подписей нет. Переменная может сегодня хранить число, через три строки — строку, а после ответа сервера — вообще пустоту. Язык это разрешает и даже не считает проблемой. Проблема появляется позже, когда программа падает в самом неожиданном месте, а причина находится в другом файле, написанном две недели назад.
// обычный JavaScript: язык не возражает
let score = 0;
score = "ноль";
score = null;
score = { value: 0 };
Каждая из этих строк работает. И каждая — потенциальная поломка: следующая функция, которая попробует прибавить к score десять очков, получит "ноль10", 10 или ошибку выполнения — в зависимости от того, что успело записаться.
TypeScript закрывает именно эту дыру. Ты один раз пишешь, что score — это число, и редактор начинает подсвечивать любую попытку положить туда что-то другое красной волной ещё до того, как ты нажал «запустить».
let score: number = 0;
score = "ноль"; // ошибка: тип 'string' не присваивается типу 'number'
Важная деталь, которая сначала сбивает с толку новичков: браузер не умеет выполнять TypeScript. Ни один браузер, ни один сервер на Node.js не понимают запись let score: number. Типы существуют только на этапе написания и проверки кода, а перед запуском удаляются — как это устроено, в следующем разделе.
Сам язык давно перестал быть экзотикой: это проект с открытым кодом, разработанный в Microsoft, на нём написаны редакторы кода, крупные библиотеки и интерфейсы магазинов. Для новичка это значит, что почти любая инструкция, пример из документации библиотеки и ответ ИИ-ассистента будут доступны и на TypeScript тоже.
Главная мысль: TypeScript не заменяет JavaScript и не спорит с ним. Он добавляет к нему слой описаний, который работает как страховка от опечаток и неверных предположений о данных.
Чем TypeScript отличается от JavaScript и зачем нужна сборка
Различие между двумя языками сводится к одному вопросу: когда обнаруживается ошибка. В JavaScript — во время выполнения, когда программа запущена и пользователь нажал кнопку. В TypeScript — во время написания, пока файл открыт в редакторе.
Ошибка во время выполнения против ошибки в редакторе
Возьмём функцию, которая прибавляет очки за съеденное яблоко в «Змейке».
function addPoints(score, bonus) {
return score + bonus;
}
addPoints(120, 10); // 130 — правильно
addPoints("120", 10); // "12010" — тихая поломка
Второй вызов не выдаёт ошибку — он выдаёт неверный результат, который поедет дальше: попадёт в таблицу рекордов, сохранится в браузере, отправится на сервер. Через неделю ты будешь искать, почему у одного игрока счёт из пяти цифр, и не найдёшь ничего подозрительного в коде таблицы, потому что виноват другой файл.
Та же функция на TypeScript:
function addPoints(score: number, bonus: number): number {
return score + bonus;
}
addPoints("120", 10); // ошибка прямо в редакторе, до запуска
Строка подчёркивается сразу. Ты видишь проблему в ту же секунду, когда её создал, а не через неделю по жалобе пользователя.
Компиляция: почему TypeScript нужно превращать в JavaScript
Раз браузеры не понимают типы, между твоим кодом и запуском появляется дополнительный шаг — компиляция, или сборка. Компилятор TypeScript (tsc) читает файлы с расширением .ts и .tsx, проверяет все типы и выдаёт на выходе обычные файлы .js, из которых типы просто вырезаны.
// было: greet.ts
function greet(name: string): string {
return `Привет, ${name}!`;
}
// стало: greet.js
function greet(name) {
return `Привет, ${name}!`;
}
Разница только в исчезнувших подписях. Никакого дополнительного кода в результат не добавляется, программа не становится тяжелее или медленнее — типы работают до запуска и на сам запуск не влияют.
Проверка типов и сборка при этом — разные вещи: компилятор может пожаловаться на ошибки и всё равно собрать рабочий .js, чтобы недоделанный проект можно было запустить и посмотреть. Красная волна в редакторе не всегда означает, что программа не запустится, но почти всегда означает, что где-то она поведёт себя не так, как ты ожидаешь.
Для сборки нужен установленный Node.js — среда, которая запускает JavaScript вне браузера, и вместе с ней менеджер пакетов npm. Если ты ещё не разбирался, зачем на компьютере вообще стоит Node.js и как он связан с командами в терминале, посмотри отдельный разбор в статье Что такое Node.js: без него ни компилятор TypeScript, ни сборщики проектов не поставить.
На практике вручную запускать tsc приходится редко. Современные шаблоны проектов уже включают сборку: ты запускаешь одну команду разработки, она следит за файлами, пересобирает изменённое и обновляет страницу в браузере. Компиляция становится фоновым процессом, который ты замечаешь, только когда он ругается.
Сравнение по практическим критериям
| Критерий | JavaScript | TypeScript |
|---|---|---|
| Когда ловится ошибка в данных | Во время работы программы, у пользователя | В редакторе, при написании строки |
| Нужна ли сборка | Нет, файл открывается в браузере как есть | Да, .ts компилируется в .js |
| Подсказки редактора | По догадке, часто неполные | Точные: поля объекта, аргументы, возвращаемое значение |
| Переименование поля в проекте | Поиск по тексту и надежда, что нашёл всё | Редактор находит все использования по типу |
| Скорость на маленьком проекте | Выше: нет промежуточного шага | Ниже: время на описания и сборку |
| Отдача на проекте от 500 строк | Падает: правки начинают ломать соседнее | Растёт: правки проверяются автоматически |
| Как понимает код ИИ-ассистент | По именам переменных и контексту | По явным описаниям структур данных |
| Работа в команде | Нужна устная договорённость о формате данных | Формат записан в коде и проверяется |
| Итоговый файл в браузере | Он же и есть | Тот же JavaScript, типы вырезаны |
Строчка про отдачу на проекте от пятисот строк — ключевая. На игре из одного файла типы кажутся лишней бюрократией. Как только появляются несколько модулей, сохранение рекордов и магазин скинов, всё переворачивается: без типов каждая правка становится проверкой памяти, с типами — обычной задачей.
Что такое тип и какие бывают базовые типы
Тип — это ответ на вопрос «что здесь может лежать». Не конкретное значение, а множество допустимых значений и набор операций, которые с ними разрешены. У числа можно взять квадратный корень, у строки — длину, у логического значения — ни того, ни другого.
Запись типа делается через двоеточие после имени: имя: тип. Это называется аннотацией типа.
Базовые типы, которых хватает в 90% случаев
let score: number = 0; // числа: и целые, и дробные
let playerName: string = "Аня"; // строки
let isPaused: boolean = false; // true или false
let speed: number = 1.5; // отдельного типа для дробных нет
В JavaScript нет разделения на целые и дробные числа — есть один тип number, и TypeScript это повторяет.
Массивы
Массив описывается как «тип элемента плюс квадратные скобки»:
let scores: number[] = [120, 340, 90];
let names: string[] = ["Аня", "Борис", "Вера"];
scores.push(50); // разрешено
scores.push("50"); // ошибка: в массиве чисел строке не место
Одна аннотация закрывает целый класс ошибок: ни одна строка не просочится в список результатов, а значит, сортировка не выдаст странный порядок и сумма не превратится в склейку символов.
Пустота: null и undefined
Отсутствие данных обозначают два значения: undefined — «значение не задавали», null — «значение осознанно пустое». В «Змейке» такое состояние встречается постоянно: пока игрок не сыграл ни разу, рекорда нет.
let bestScore: number | null = null; // рекорда ещё нет
bestScore = 340; // сыграл — появился
Вертикальная черта здесь читается как «или»: значение — либо число, либо пустота. Подробнее про такую запись — в разделе про объединения.
Функции: аргументы и результат
У функции описываются входы и выход:
function formatScore(score: number): string {
return `${score} очков`;
}
score: number — что принимаем, : string после скобок — что отдаём. Если внутри функции случайно вернуть число, редактор пожалуется: обещал строку.
Функция, которая ничего не возвращает, имеет тип результата void:
function playSound(name: string): void {
// проигрываем звук, ничего не отдаём наружу
}
Необязательные аргументы и значения по умолчанию
Вопросительный знак после имени делает аргумент необязательным:
function saveScore(score: number, playerName?: string): void {
const who = playerName ?? "Гость";
// сохраняем
}
saveScore(120); // можно так
saveScore(120, "Аня"); // и так
Внутри такой функции playerName имеет тип string | undefined, и TypeScript не даст обратиться к его длине, пока ты не проверишь наличие значения. Первые дни это раздражает, потом спасает: так выглядит добрая половина ошибок «cannot read property of undefined» в живых проектах.
Вывод типов: писать аннотации нужно не везде
Главное недоразумение новичков — представление, что каждую переменную придётся подписывать вручную. Это не так. TypeScript сам догадывается о типе по присвоенному значению:
let score = 0; // выведен тип number, аннотация не нужна
const playerName = "Аня"; // выведен тип "Аня" — константа не изменится
const scores = [1, 2, 3]; // выведен тип number[]
Аннотации ставят там, где догадка невозможна или опасна: у аргументов функций, у пустых массивов, у данных извне — из хранилища браузера, ответа сервера, формы. Правило для старта: описывай границы программы, внутри доверяй выводу типов.
Специальные типы, о которых стоит знать
any — «что угодно, проверки отключены»; про него отдельный разговор ниже, и он невесёлый. unknown — «пока неизвестно, но проверить придётся»: положить в такую переменную можно всё, а использовать только после проверки. never — «сюда выполнение не дойдёт», встречается в функциях, которые всегда выбрасывают ошибку; новичку достаточно узнавать его в сообщениях компилятора.
Типы объектов, интерфейсы и объединения на примере рекордов «Змейки»
Отдельные числа и строки — только начало. Настоящая польза появляется, когда описываешь структуру данных целиком. Возьмём задачу из курса: таблица рекордов, где у каждой записи есть имя игрока, счёт и дата.
Тип объекта
type ScoreRecord = {
player: string;
score: number;
date: string;
};
const record: ScoreRecord = {
player: "Аня",
score: 340,
date: "2026-09-09",
};
Слово type создаёт псевдоним типа: теперь ScoreRecord можно использовать везде, где раньше пришлось бы расписывать три поля заново. Забудешь поле или напишешь scores вместо score — редактор подчеркнёт объект целиком и покажет, чего не хватает. Заодно описание работает как документация: через месяц не придётся вспоминать, что лежит в записи рекорда, тип виден по наведению курсора.
Массив записей и функции над ним
type ScoreRecord = {
player: string;
score: number;
date: string;
};
function topThree(records: ScoreRecord[]): ScoreRecord[] {
return [...records]
.sort((a, b) => b.score - a.score)
.slice(0, 3);
}
Внутри функции редактор знает про a и b всё: предложит score, но не предложит несуществующее points. Опечатка в имени поля перестаёт быть невидимой.
Интерфейсы
Второй способ описать объект — interface:
interface ScoreRecord {
player: string;
score: number;
date: string;
}
Разница с type для новичка почти нулевая. Практическое отличие в том, что интерфейс можно дополнить позже, объявив его ещё раз с новыми полями, — так библиотеки расширяют чужие описания; псевдоним типа переобъявить нельзя, зато он описывает не только объекты, но и объединения, кортежи, функции. Рабочее правило: объекты через interface, всё остальное через type, а если проект уже начат в одном стиле — следуй ему.
Необязательные поля и readonly
Не у каждой записи есть все данные. Игрок мог не вводить имя, а гостю его подставили автоматически:
interface ScoreRecord {
player: string;
score: number;
date: string;
skin?: string; // необязательное поле
readonly id: string; // менять после создания нельзя
}
readonly защищает от случайной перезаписи: попытка присвоить новое значение полю id вызовет ошибку — удобно для идентификаторов и настроек, которые задаются один раз.
Объединения: когда значений несколько вариантов
Объединение (union) — это перечисление допустимых вариантов через вертикальную черту. Такой приём точнее всего описывает состояния игры.
type GameState = "menu" | "playing" | "paused" | "gameover";
let state: GameState = "menu";
state = "playing"; // хорошо
state = "pause"; // ошибка: опечатка поймана
Вместо абстрактной строки, куда можно записать что угодно, получился список из четырёх значений. Редактор подсказывает варианты при наборе, а опечатка в одной букве больше не превращается в ночь отладки, когда игра просто перестаёт реагировать на паузу.
То же самое работает для направления движения змейки:
type Direction = "up" | "down" | "left" | "right";
const opposite: Record<Direction, Direction> = {
up: "down",
down: "up",
left: "right",
right: "left",
};
Встроенный тип Record требует, чтобы в объекте были описаны все четыре направления. Добавишь пятое в Direction и забудешь про него здесь — компилятор напомнит. Такая проверка «ничего не забыто» ценнее всего при расширении готового кода.
Сужение типов
Когда значение может быть двух видов, TypeScript требует проверки перед использованием. Проверил — тип автоматически «сужается» до конкретного:
function showBest(best: number | null): string {
if (best === null) {
return "Рекорда пока нет";
}
return `Лучший результат: ${best} очков`;
}
После if компилятор уверен, что best — число. Этот же механизм ловит попытки прочитать поле у того, чего нет.
Данные из хранилища браузера
Самое опасное место в игре — чтение сохранённых рекордов из localStorage. Оттуда всегда приходит строка либо null, а что внутри строки — не знает никто: файл мог остаться от старой версии игры, пользователь мог его отредактировать вручную.
function loadRecords(): ScoreRecord[] {
const raw = localStorage.getItem("snake-records");
if (!raw) return [];
try {
const parsed: unknown = JSON.parse(raw);
return Array.isArray(parsed) ? (parsed as ScoreRecord[]) : [];
} catch {
return [];
}
}
Проверка на массив и перехват ошибки разбора — минимум, без которого игра падает на первом же испорченном значении. Многие поломки в опубликованных играх живут именно тут: в предположении, что в хранилище лежит то же самое, что клали туда неделю назад.
Зачем типы нужны, когда код пишет ИИ
Логичное возражение новичка звучит так: ИИ-ассистент пишет код за меня, я даже переменные сам не называю — зачем мне дополнительная работа с описаниями? Ответ парадоксальный: чем больше кода пишет ИИ, тем ценнее типы. И причин тут три.
Причина первая: ты не читаешь весь код глазами
При работе с ИИ-ассистентом ты ставишь задачу словами, получаешь пятьдесят строк и проверяешь результат в браузере. Построчного чтения не происходит — в этом и смысл подхода, который называют вайбкодингом. Значит, роль контролёра, раньше принадлежавшая твоим глазам, должна перейти к чему-то другому.
Типы и берут её на себя. Если ассистент передал в функцию строку вместо числа, перепутал порядок аргументов или обратился к несуществующему полю, редактор подчеркнёт это сразу после вставки кода — без запуска и без «попробуем и посмотрим». Особенно заметно на длинных сессиях: без типов проблема всплывает через двадцать сообщений, когда ассистент уже трижды переписал соседние файлы и восстановить причину сложно даже ему, а с типами она не доживает до второго сообщения.
Причина вторая: ИИ получает более чёткий контекст и меньше фантазирует
Модель, которая пишет код, видит твой проект как текст. Из чего она понимает, что лежит в переменной data? Из имени, из соседних строк, из общей вероятности. Это догадка, и иногда она неверна — тогда появляется вызов метода, которого у объекта нет, или обращение к полю userName там, где на самом деле player.
Описанный интерфейс ScoreRecord превращает догадку в факт: ассистент не изобретает, как называется поле с именем игрока, а читает. Просьба «добавь сортировку рекордов по дате» выполняется по описанию, а не по интуиции.
Работает и обратная связь: ошибки типов возвращаются ассистенту как готовое техническое задание. Формулировка «поле date отсутствует в типе ScoreRecord» гораздо полезнее для модели, чем твоё «что-то сломалось, таблица пустая». Передаёшь точный текст ошибки — получаешь точную правку.
Причина третья: типы фиксируют договорённости между частями программы
«Змейка» из курса быстро перестаёт быть одним файлом: появляется игровое поле, счётчик, хранилище рекордов, магазин скинов, звук. Пока формат обмена данными живёт только в голове, любая правка в одном месте рискует сломать другое — и ИИ-ассистент, правящий файл по твоей просьбе, об этой договорённости не знает.
Записанный тип и есть та самая договорённость, зафиксированная в коде. Меняешь формат записи рекорда — компилятор показывает все места, которые перестали ему соответствовать. Получается конкретный список задач, который можно отдать ассистенту целиком.
Что типы не делают
Типы не проверяют смысл. Функция addPoints(score: number, bonus: number): number, которая внутри вычитает вместо сложения, пройдёт проверку типов без замечаний: числа на входе, число на выходе, всё формально верно. Логику проверяют запуском и автотестами — как их завести новичку, разбирается в статье про автотесты для новичков. Типы и тесты закрывают разные дыры: первые — про форму данных, вторые — про поведение программы.
Отсюда и здравая позиция: типы не заменяют проверку результата в браузере, а сокращают количество поводов туда лезть.
Как включить TypeScript в проект
Способ зависит от того, начинаешь ты с нуля или добавляешь типы к готовой игре. Разберём оба, без привязки к конкретным версиям — они меняются, а порядок действий остаётся.
Новый проект
Проще всего взять готовый шаблон: современные сборщики создают заготовку с уже настроенным TypeScript одной командой в терминале. Ручная настройка с нуля новичку ничего не даёт. Внутри такой заготовки будут исходники с расширением .ts (или .tsx, если есть React-разметка), файл настроек tsconfig.json, package.json с командами запуска и пакет typescript в зависимостях разработки.
Добавление TypeScript к существующему проекту
Игра уже написана на JavaScript и работает — ломать её не нужно. Порядок такой:
- Установить компилятор как зависимость разработки:
npm install --save-dev typescript
- Создать файл настроек:
npx tsc --init
Команда сгенерирует tsconfig.json с подробными комментариями к каждому параметру.
- Настроить основные параметры. Для начала достаточно нескольких:
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"strict": true,
"allowJs": true,
"checkJs": false,
"outDir": "dist",
"skipLibCheck": true
},
"include": ["src"]
}
Что тут важно новичку. allowJs разрешает держать в проекте оба вида файлов одновременно — это и есть ключ к постепенному переезду. strict включает строгие проверки: с ним TypeScript действительно защищает, без него превращается в украшение. outDir — папка, куда попадут собранные .js. include — где искать исходники.
Переименовывать файлы по одному:
game.js→game.ts. После каждого переименования появится порция ошибок — это нормально, они и были там всё время, просто их никто не показывал. Исправляешь, переходишь к следующему файлу.Запускать проверку:
npx tsc --noEmit
Флаг --noEmit означает «только проверь, ничего не собирай». Удобно держать эту команду под рукой и запускать перед каждой публикацией игры.
С чего начинать переезд
Порядок файлов имеет значение: начинай с тех, которые ни от чего не зависят — утилиты, форматирование счёта, работа с хранилищем, — и только потом берись за тех, кто их использует. Обратный путь мучителен, потому что типы верхнего уровня будут спорить с неописанным нижним. И не пытайся перевести всё за вечер: смешанный проект, где половина файлов на TypeScript, а половина на JavaScript, полностью работоспособен, это штатный режим.
Строгий режим: включать сразу или потом
strict: true включает набор проверок, главная из которых — strictNullChecks: она требует явно обрабатывать случаи, когда значения может не быть, и отвечает за большую часть пользы от типов вместе с большей частью первоначального раздражения.
В новом проекте включай сразу. При переезде готовой игры можно начать без строгого режима, довести проект до состояния «ошибок нет», а потом включить строгость и разобрать вторую волну замечаний. Оставлять strict: false навсегда смысла нет: без него TypeScript пропускает ровно те ошибки, ради которых его ставили.
Типы для чужих библиотек
Многие библиотеки поставляются с описаниями типов внутри — делать ничего не нужно. Для старых пакетов описания живут отдельно и ставятся пакетом с приставкой @types/. Если редактор жалуется, что не может найти объявления для импортируемого модуля, попробуй в первую очередь это:
npm install --save-dev @types/имя-пакета
TypeScript и React: типизация пропсов и состояния
Связка React и TypeScript встречается чаще всего: интерфейсы состоят из компонентов, компоненты обмениваются данными, и ошибки в этих данных дороже всего. Файлы с React-разметкой получают расширение .tsx вместо .ts — это единственное отличие в правилах именования.
Пропсы компонента
Пропсы — это входные данные компонента, его аргументы. Описываются они как обычный объект:
interface ScoreBoardProps {
records: ScoreRecord[];
currentPlayer: string;
onReset?: () => void;
}
function ScoreBoard({ records, currentPlayer, onReset }: ScoreBoardProps) {
return (
<div className="scoreboard">
<h2>Рекорды</h2>
<ol>
{records.map((record) => (
<li key={record.player + record.date}>
{record.player}: {record.score}
</li>
))}
</ol>
{onReset && <button onClick={onReset}>Сбросить</button>}
</div>
);
}
Запись onReset?: () => void описывает необязательный обработчик: функция без аргументов, ничего не возвращающая. Теперь если ты используешь компонент и забудешь передать records, редактор подчеркнёт вызов и скажет, чего не хватает. Раньше такая ошибка проявлялась пустым экраном и падением в консоли.
Внутри компонента record тоже типизирован — редактор предлагает player, score, date и ничего лишнего. Опечатка record.scores подсвечивается мгновенно.
Состояние
Хук useState в большинстве случаев выводит тип сам:
const [score, setScore] = useState(0); // number
const [isPaused, setIsPaused] = useState(false); // boolean
Аннотация нужна там, где начальное значение не описывает всех вариантов, — типичный случай для загрузки данных:
const [records, setRecords] = useState<ScoreRecord[]>([]);
const [best, setBest] = useState<number | null>(null);
const [state, setState] = useState<GameState>("menu");
Первый пример особенно важен: без аннотации пустой массив был бы понят как never[] и положить туда запись не вышло бы. Третий не даст записать в состояние игры значение, которого нет в списке.
События
Обработчики событий тоже типизируются. Самая частая пара:
function handleKeyDown(event: React.KeyboardEvent<HTMLDivElement>) {
if (event.key === "ArrowUp") setDirection("up");
}
function handleNameChange(event: React.ChangeEvent<HTMLInputElement>) {
setPlayerName(event.target.value);
}
Записи выглядят длинно, но набирать их вручную приходится редко: редактор подставляет тип автоматически, если обработчик написан прямо в разметке. А польза ощутимая — event.target.value существует для поля ввода и не существует для произвольного элемента, и TypeScript про это знает.
Отдельная история — формы. Как только полей становится больше двух и появляется валидация, ручные обработчики превращаются в кашу, и на помощь приходят специальные библиотеки; про один из таких подходов написано в статье про формы с валидацией на React Hook Form. Типизированная форма удобна тем, что список полей и правила проверки описываются один раз и дальше проверяются автоматически.
Общие типы в отдельном файле
Когда компонентов становится много, типы данных выносят в отдельный файл src/types.ts и импортируют оттуда:
import type { ScoreRecord } from "./types";
Запись import type подчёркивает, что импортируется только описание, которое исчезнет при сборке. Такой файл заодно становится картой проекта: открыл — увидел, какими сущностями оперирует игра.
Что делать с ошибками типов и почему any — плохая привычка
Первая неделя с TypeScript выглядит как поток красных подчёркиваний. Это нормальная фаза, через которую проходят все. Разберём, как её пережить без потерь.
Как читать сообщение об ошибке
Сообщения компилятора длинные, но устроены одинаково: сначала суть, потом уточнения. Два самых частых вида выглядят так:
Type 'string' is not assignable to type 'number'.
Object is possibly 'null'.
Первое читается буквально: «пытаешься положить строку туда, где ожидается число». Второе означает «здесь может не быть значения, ты этого не учёл», и лечится проверкой перед использованием — тем самым сужением типа из раздела выше. Девять из десяти ошибок новичка сводятся к этой паре: перепутанные строка и число, забытая проверка на пустоту, опечатка в имени поля.
Если сообщение непонятное, скопируй его целиком вместе с проблемной строкой и отдай ИИ-ассистенту. Текст ошибки компилятора — точные входные данные, по ним ассистент даёт хорошее объяснение и правку.
Почему хочется написать any и почему не нужно
Тип any отключает проверки для конкретного значения. Написал — и красная волна пропала:
let data: any = JSON.parse(raw);
data.player.name.first.toUpperCase(); // ошибок нет — до запуска
Соблазн понятен: работа встала, а any убирает препятствие мгновенно. Проблема в том, что препятствие было предупреждением. Проверки исчезли, данные не изменились — программа по-прежнему упадёт, просто теперь у пользователя, а не в редакторе.
Хуже другое: any заразен. Значение с этим типом расползается по программе через результаты функций, и через месяц половина проекта оказывается непроверяемой, хотя формально написана на TypeScript. Худший вариант из двух: расходы на сборку остались, защита пропала.
Чем заменить any
Есть честные инструменты на каждый случай. unknown — когда тип действительно неизвестен: положить можно всё, использовать только после проверки.
const parsed: unknown = JSON.parse(raw);
if (typeof parsed === "object" && parsed !== null && "score" in parsed) {
// теперь с parsed можно работать осмысленно
}
Объединение — когда вариантов несколько, но они известны: number | null, string | number. Частичный тип Partial<ScoreRecord> — когда объект заполнен не целиком, он делает все поля необязательными. Приведение через as — когда ты действительно знаешь больше компилятора; оно ничего не проверяет, а просто говорит «поверь мне», поэтому такие места держи на границах программы и сопровождай проверкой значений.
Если срочно нужно заглушить одну конкретную строку, лучше any подходит комментарий-исключение:
// @ts-expect-error — временно, до правки типов библиотеки
legacyWidget.init({ theme: "dark" });
Он точечный, виден при чтении, и компилятор сам пожалуется, когда ошибка исчезнет и подавление станет ненужным.
Ошибки после генерации кода ИИ
Отдельный сценарий: ассистент выдал большой кусок кода, а редактор его весь подчеркнул. Не исправляй руками — верни ошибки автору: скопируй текст замечаний и попроси привести код в соответствие с существующими типами проекта, назвав нужный тип по имени. Одного круга хватает почти всегда.
Потом проверь, не решил ли ассистент проблему через any или приведение as. Модель ищет способ убрать сообщение об ошибке, а самый короткий способ — отключить проверку; просьба «исправь типы без использования any» обычно даёт нормальный результат. Если и два круга не помогли, разбирайся сам: как правило, к этому моменту видно, что проблема не в типах, а в структуре данных, придуманной на ходу.
Когда TypeScript не нужен
Честный ответ: не всегда. Есть ситуации, где типы отнимают больше, чем дают.
Одностраничная проба на тридцать строк. Проверить идею, посмотреть, как работает библиотека, набросать анимацию — открывай .js и пиши: настройка сборки ради получаса экспериментов не окупается. Сюда же скрипты-однодневки: разовая обработка файла, утилита для себя, код, который выполнится один раз и будет удалён.
Первая «Змейка», собранная за вечер. Пока игра живёт в одном файле и её целиком видно на двух экранах, ты сам и есть система проверки типов. Учебная ценность быстрого результата выше, чем польза от описаний.
Проект с жёстким сроком и командой без опыта. Если до сдачи неделя, а никто не работал с типами, переезд посреди дороги добавит проблем, а не уберёт их. Отложи до следующей итерации.
Где TypeScript окупается почти всегда: проект живёт дольше месяца, файлов больше пяти, над кодом работает не один человек, данные приходят извне — с сервера, из хранилища, из формы. И отдельно — когда значительную часть кода пишет ИИ-ассистент, а ты проверяешь результат, а не каждую строку.
Компромисс для новичка простой: собери первую версию игры на JavaScript и опубликуй, а когда появится желание добавить магазин скинов, рекорды на сервере и офлайн-режим, переводи проект на TypeScript — к этому моменту уже увидишь, зачем.
Частые вопросы
Нужно ли сначала выучить JavaScript, чтобы работать с TypeScript?
Базовое понимание JavaScript нужно: переменные, функции, объекты, массивы. TypeScript не заменяет эти основы, он добавляет к ним слой описаний. Отдельного «курса TypeScript с нуля» проходить не обязательно — синтаксис типов осваивается по ходу дела, за пару недель практики.
TypeScript замедляет работу программы?
Нет. Типы удаляются при сборке, в браузер попадает обычный JavaScript, байт в байт такой же, как если бы ты написал его руками. Замедляется другое — процесс разработки: появляется шаг компиляции, который занимает от долей секунды до нескольких секунд в зависимости от размера проекта.
Можно ли смешивать файлы .js и .ts в одном проекте?
Можно, и это штатный способ переезда. Параметр allowJs в tsconfig.json разрешает держать оба вида файлов рядом. Переименовывай по одному, начиная с независимых утилит, и живи в смешанном состоянии столько, сколько нужно.
ИИ-ассистент лучше пишет код на TypeScript или на JavaScript?
Качество генерации сопоставимо, оба языка широко представлены в обучающих данных. Разница в другом: на TypeScript ошибки в сгенерированном коде видны сразу, а сам ассистент опирается на явные описания структур вместо догадок по именам переменных.
Что делать, если ошибок типов слишком много и работа встала?
Не глуши их через any. Сначала отключи строгий режим (strict: false), доведи проект до нуля ошибок, потом включай строгость обратно и разбирай замечания порциями. Если ошибка одна и совсем непонятная, скопируй её текст вместе со строкой кода и попроси ИИ-ассистента объяснить и починить.
Обязателен ли TypeScript для React?
Нет, React прекрасно работает и на JavaScript. Но связка с TypeScript популярна не случайно: пропсы компонентов и состояние — ровно те места, где ошибки в данных случаются чаще всего и обнаруживаются позже всего. Начинать новый React-проект стоит сразу с TypeScript, шаблоны для этого готовы.
Заключение
Типы решают одну задачу — переносят обнаружение ошибок из будущего в настоящее, из браузера пользователя в твой редактор. Когда код пишет ИИ, ценность такого переноса только растёт: ты перестаёшь читать каждую строку, значит нужен автоматический контролёр, который читает вместо тебя и разговаривает с ассистентом на языке точных сообщений об ошибках.
Путь новичка не требует героизма. Собери первую игру на JavaScript и доведи до публикации, а когда появятся второй файл, таблица рекордов и желание что-то серьёзно поменять, поставь typescript, включи strict и переименуй первый файл. Как правило, находится пара ошибок, которые давно жили в проекте и ждали своего часа.
Источники
- TypeScript — официальная документация: typescriptlang.org/docs
- TypeScript Handbook — «Everyday Types»: typescriptlang.org
- React — «Using TypeScript»: react.dev/learn/typescript
Читай дальше
Все статьиНе просто статьи — тебя доведут до результата
В практикуме за 2499 ₽ рядом живая команда практикующих разработчиков и маркетологов: ведём по шагам до твоего работающего приложения. Не «ролики и сам разбирайся» — помогаем на каждом затыке.
Перейти к практикуму