Три года назад TypeScript в вакансии «middle-фронтенд» писали в разделе «будет плюсом». Сейчас его пишут в разделе «требования», а без TS осталось разве что легаси, которое никто не хочет трогать. Node.js с версии 22.6 запускает .ts-файлы нативно (через --experimental-strip-types), Deno делает это без флагов, Bun — тоже. Дебаты «типы в JavaScript — хорошо или плохо» закончились. Индустрия проголосовала кодом.
Из-за этого поменялся и вопрос. Раньше стоял «зачем учить TS». Сейчас — «как войти правильно, чтобы не бросить на первом же дженерике». Разбираем на конкретных примерах.
Что такое TypeScript — на живых примерах
TypeScript — это надстройка над JavaScript, которая добавляет систему типов. Технически — отдельный язык, но синтаксис 95% совпадает с JS: любой валидный JS-файл — валидный TS-файл. Разница в том, что типы записываются явно, а компилятор проверяет их до запуска кода. В браузере или Node TypeScript сам не выполняется — компилятор tsc сначала стирает типы (получая обычный JS), потом уже этот JS исполняется.
// JavaScript — тип известен только в рантайме
function greet(name) {
return `Привет, ${name.toUpperCase()}`;
}
greet(42); // рантайм-ошибка: name.toUpperCase is not a function
// TypeScript — компилятор ловит несовместимость до запуска
function greetTs(name: string): string {
return `Привет, ${name.toUpperCase()}`;
}
greetTs(42); // ← подчёркивает красным в IDE:
// Argument of type 'number' is not assignable to parameter of type 'string'.
Ключевое отличие — когда именно ловится ошибка. В JS — когда пользователь уже открыл страницу и нажал на кнопку. В TS — когда программист ещё пишет код и IDE подчёркивает строку.
Из каких кирпичей строится система типов
Начальный набор, который покрывает 80% ежедневного кода.
Базовые типы. Примитивы string, number, boolean, null, undefined. Массивы через T[] или Array<T>. Объекты — через interface или type:
interface User {
id: number;
name: string;
email: string;
isActive: boolean;
tags: string[]; // массив строк
createdAt: Date;
}
// то же самое через type — почти равнозначно:
type UserT = {
id: number;
name: string;
/* ... */
};
Объединения (union). Значение может быть одним из нескольких типов — классический случай, когда функция возвращает или данные, или ошибку.
type Status = 'pending' | 'success' | 'error'; // строковый литерал-union
function getStatus(): Status { return 'success'; }
// Значение может быть либо User, либо null (не пришло из API)
function findUser(id: number): User | null {
return database.find(id) ?? null;
}
// Использование: TS заставит явно проверить null перед доступом
const user = findUser(1);
console.log(user.name); // Error: user is possibly null
if (user) console.log(user.name); // OK
Дженерики (generics). Способ описать функцию или структуру, которая работает с любым типом, но сохраняет его для проверок. Массивы, промисы, коллекции — все построены на дженериках.
function first<T>(arr: T[]): T | undefined {
return arr[0];
}
const firstName = first(['Аня', 'Борис']); // TS выводит: string | undefined
const firstNum = first([1, 2, 3]); // TS выводит: number | undefined
firstName.toUpperCase(); // OK — компилятор знает про string
firstNum.toUpperCase(); // Error — нет метода toUpperCase у number
Что TS ловит, а что нет
Легко считать, что типы делают код неуязвимым. Не делают — они гарантируют одну конкретную вещь.
Ловит: опечатки в именах свойств (user.emial вместо user.email), несоответствие типов при вызовах функций, забытые ветки в switch над union-типом, доступ к возможно-null-значению без проверки, попытку использовать await на не-промис. Это большой класс ошибок, который в JS находят только через тесты или в проде.
Не ловит: рантайм-ошибки природы, которые в принципе не выразимы в типах. Fetch к сети упал — TS про это не узнает. JSON.parse получил на вход невалидный текст — тоже. Деление на ноль. Логические баги в бизнес-правилах. TS — про статическую корректность структур, а не про правильность решений.
Классический паттерн «проверенная полнота ветвлений» (exhaustiveness check) — штука, которую невозможно сделать в JS, а в TS она работает через never:
type Shape =
| { kind: 'circle'; radius: number }
| { kind: 'square'; side: number }
| { kind: 'triangle'; base: number; height: number };
function area(s: Shape): number {
switch (s.kind) {
case 'circle': return Math.PI * s.radius ** 2;
case 'square': return s.side ** 2;
case 'triangle': return s.base * s.height / 2;
default:
// Если в Shape добавят новый вариант и забудут case здесь,
// TS ругнётся: "Argument of type 'X' is not assignable to type 'never'"
const _exhaust: never = s;
throw new Error(`Unhandled shape: ${_exhaust}`);
}
}
Добавили в тип Shape новую фигуру — компилятор автоматически ткнёт носом во все switch, где новый вариант не обработан. Это одна из главных вещей, ради которых люди уходят с JS и не возвращаются.
Что даёт TS, чего нет в чистом JS
Типы — это не только про проверки. Они меняют понимание кода редактором IDE.
Автокомплит по типам API. Написал user. — выпадает список именно тех полей, которые у объекта есть. Не список из всех идентификаторов проекта, как в JS-режиме, а точный набор. Никаких «а что там за поля были» и походов в другой файл.
Refactor-safe переименование. Правильно настроенная IDE (VSCode с TypeScript-плагином, IntelliJ) переименует user.name → user.fullName во всём проекте ровно там, где это то же самое поле. Локальную переменную name в другом файле не тронет. В JS с этим справится только текстовый search-and-replace, который путает переменные.
Типы как живая документация. Файл types.ts с описанием форматов API — это спецификация, которую невозможно оставить устаревшей: если формат разошёлся с типом, компиляция падает. В JS такую спецификацию пишут в JSDoc или в отдельном README, и её никто не обновляет.
// types/api.ts — единственный источник правды про формат ответа API
export interface ArticleResponse {
id: number;
slug: string;
title: string;
publishedAt: string; // ISO 8601
tags: string[];
author: {
id: number;
name: string;
};
}
// в другом файле:
import { ArticleResponse } from './types/api';
async function fetchArticle(slug: string): Promise<ArticleResponse> {
const r = await fetch(`/api/articles/${slug}`);
return r.json();
// возвращаемый тип уже проверен компилятором
}
Свежие фичи TS 5.x, которых нет в статьях прошлых лет
satisfies-оператор. Хочешь проверить, что значение соответствует типу, но сохранить его конкретный (литеральный) вид? В старом TS для этого использовали приведение as, которое ломает вывод типов — satisfies решает эту проблему.
type Colors = Record<string, string | [number, number, number]>;
// с "as" — компилятор забывает, что brand это литеральный кортеж
const palette1 = {
brand: [14, 51, 111],
accent: '#ffe066',
} as Colors;
palette1.brand[0]; // Error: Property '0' does not exist on type 'string | [number, number, number]'
// с "satisfies" — тип проверен, но конкретика сохранена
const palette2 = {
brand: [14, 51, 111],
accent: '#ffe066',
} satisfies Colors;
palette2.brand[0]; // OK, TS знает что brand — кортеж
const-параметры-типов. Позволяют вывести самый узкий возможный тип у аргумента-константы, не заставляя пользователя писать as const самому.
// без const
function pick<T extends string>(options: T[]): T { return options[0]; }
const a = pick(['red', 'green']); // TS выводит: string
// с const
function pickC<const T extends string>(options: T[]): T { return options[0]; }
const b = pickC(['red', 'green']); // TS выводит: 'red' | 'green' — точные литералы
Template literal types. Позволяют строить строковые типы из других типов — часто применяется в утилитах вроде маршрутизаторов, где адрес URL описывается через тип.
type Route = `/posts/${string}` | `/manuals/${string}/${string}`;
const r1: Route = '/posts/typescript-2026'; // OK
const r2: Route = '/manuals/preobrazovanie-tipov/glava-1'; // OK
const r3: Route = '/random'; // Error
Когда TS избыточен
Не в каждой задаче типы окупаются — иногда чистый JS быстрее по времени и не хуже по надёжности.
- Прототип на выходные — идея «посмотрю, что получится». Если прототип докажет себя — переведёшь позже. Если нет — сэкономил день на конфигах.
- One-off скрипты — миграция данных, разовая обработка CSV, deploy-хелпер. Все они живут одну неделю, никого больше не касаются, ошибки локализованы.
- Ad-hoc DevOps-код — глобальные обёртки для gh, docker, gcloud. Часто пишутся в bash или python-е, JS здесь — уже избыточен, TS — тем более.
- Демо-код в статьях (вроде примеров в этой статье) — лишний шум типов затрудняет чтение. Иронично, но так.
Порог, за которым TS начинает окупаться — примерно 3-5 файлов и/или команда из двух и более человек. Дальше рост дивидендов резко ускоряется.
Как войти в TS в 2026 — путь миграции
Полный флип с JS на TS одномоментно — редко удачная идея. Рекомендуемый путь — постепенный, четырьмя шагами.
Шаг 1: JSDoc-типы в JS с проверкой. Добавляем в tsconfig.json флаг checkJs: true и пишем типы через JSDoc-комментарии. Файлы остаются .js, но TS-компилятор их проверяет.
// utils.js — обычный JS
/**
* @param {number} n
* @returns {string}
*/
function formatMoney(n) {
return new Intl.NumberFormat('ru').format(n);
}
formatMoney("42"); // TS подсветит в IDE, хотя файл — .js
Шаг 2: смесь JS и TS. Флаг allowJs: true позволяет TS импортировать .js-файлы. Так можно постепенно переводить проект: новые файлы пишешь на TS, старые остаются JS, и всё это компилируется вместе.
Шаг 3: файл за файлом. Переименовываешь utils.js в utils.ts, добавляешь явные типы, фиксишь появившиеся ошибки. Дальше следующий файл. Так можно мигрировать big-codebase за несколько месяцев без блокировки разработки новых фич.
Шаг 4: включаешь strict: true. Финальный шаг — включить строгий режим, который ловит все any-неявности, null-небезопасности и т.д. Это уже полноценный TS-проект.
Что нужно из инструментов: tsc для компиляции (входит в пакет typescript), tsx или встроенный Node --experimental-strip-types для запуска .ts без предварительной компиляции. Раньше был ts-node — сейчас медленнее, но всё ещё работает. Vite / Next / Deno / Bun — в них TypeScript встроен по умолчанию.
Где пощупать. TypeScript Playground — онлайн-редактор от команды TS, где можно набрать сниппет, увидеть подсказки компилятора и результат транспиляции. Идеально для проверки идеи в один клик. Официальный туториал — TypeScript Handbook, читается за пару вечеров и покрывает 80% ежедневных задач.
Комментарии (0)