TypeScript — это JavaScript, к которому добавили типы. Типы — это правила о том, какие значения может принимать переменная, аргумент или результат функции. Компилятор проверяет эти правила до запуска кода и подсказывает прямо в редакторе. Разберём базовые типы по порядку: с чего начинать «с нуля» бэкенд-разработчику.
Зачем TypeScript поверх JavaScript
В JavaScript ошибки в работе с данными всплывают только во время выполнения. Опечатался в имени поля, передал строку туда, где ждали число, забыл, что функция иногда возвращает undefined — и узнаёшь об этом, когда сервис уже упал на проде.
TypeScript добавляет слой проверок до запуска. Компилятор tsc читает аннотации типов и ругается на несовместимости заранее.
function double(n: number): number {
return n * 2;
}
double("10"); // ошибка ещё до запуска: строка вместо number
Важная деталь: типы существуют только на этапе компиляции. После сборки они стираются, и в рантайме выполняется обычный JavaScript — никаких типов в готовом коде нет. То есть TypeScript защищает разработчика, но не проверяет данные, пришедшие извне (из HTTP-запроса, из базы). Внешние данные всё равно нужно валидировать руками.
Короткая формула: TypeScript = JavaScript + проверка типов на этапе сборки.
Аннотации типов
Аннотация — это запись : тип после имени. Так разработчик явно говорит компилятору, что здесь ожидается.
const port: number = 8080;
const host: string = "localhost";
const debug: boolean = false;
function greet(name: string): string {
return `Привет, ${name}`;
}
Базовые типы данных: number (целые и дробные — отдельного типа для целых нет), string, boolean, null, undefined. Массивы записываются как number[] или string[]. Объекты описываются перечислением полей.
const ids: number[] = [1, 2, 3];
const user: { id: number; name: string } = {
id: 1,
name: "Аня",
};
Вывод типов
Аннотации не нужны везде. TypeScript умеет сам выводить тип из значения — это называется type inference (вывод типов).
const port = 8080; // выведен number
const host = "localhost"; // выведен string
const ids = [1, 2, 3]; // выведен number[]
Здесь port всё равно имеет тип number, хотя аннотации нет: компилятор посмотрел на значение справа. Поэтому аннотации обычно ставят там, где вывод невозможен или ненадёжен — на аргументах функций и иногда на её результате. Внутри тела функции и для локальных переменных чаще полагаются на вывод.
Короткая формула: аннотируй границы (аргументы, возвращаемое значение), внутри доверяй выводу.
type и interface
Когда форма объекта повторяется, ей дают имя. Есть два способа: type и interface.
interface User {
id: number;
name: string;
}
type Product = {
id: number;
price: number;
};
Оба описывают форму объекта, и в этом случае они почти взаимозаменяемы. Разница в деталях:
interfaceможно расширять черезextendsи дополнять повторным объявлением с тем же именем (объявления сливаются). Это удобно для публичных контрактов и описаний из библиотек.typeумеет больше: он даёт имя не только объекту, но и любому типу — union, пересечению, кортежу, примитиву.
type Id = number; // псевдоним для примитива
type Point = [number, number]; // кортеж
type Status = "active" | "banned"; // union (см. ниже)
Практичное правило для начала: для формы объекта бери interface, для всего остального (union, псевдонимы, комбинации) — type. Команды часто выбирают что-то одно для единообразия — это тоже нормально.
Объединения и пересечения
Объединение (union) через | означает «либо то, либо это».
type Id = number | string;
function findUser(id: number | string) {
// id может быть и числом, и строкой
}
Частый случай — значение, которого может не быть:
let token: string | null = null;
token = "abc123";
Пересечение (intersection) через & означает «и то, и это одновременно» — объединяет поля нескольких типов в один.
interface HasId {
id: number;
}
interface HasTimestamps {
createdAt: Date;
}
type Entity = HasId & HasTimestamps;
// Entity имеет и id, и createdAt
const row: Entity = {
id: 1,
createdAt: new Date(),
};
Литеральные типы и опциональные поля
Литеральный тип — это тип, состоящий из одного конкретного значения. Сам по себе он используется редко, но в составе union превращается в удобный набор допустимых вариантов (аналог перечисления).
type Role = "admin" | "user" | "guest";
function setRole(role: Role) {
// сюда нельзя передать произвольную строку,
// только одно из трёх значений
}
setRole("admin"); // ок
setRole("owner"); // ошибка: нет такого варианта
Опциональное поле помечается знаком ? — его можно не указывать. Тип такого поля автоматически становится «значение или undefined».
interface CreateUser {
name: string;
email?: string; // необязательное поле
}
const a: CreateUser = { name: "Аня" }; // ок
const b: CreateUser = { name: "Боб", email: "b@x.io" }; // тоже ок
any, unknown и never
Три специальных типа, которые важно различать.
any — «отключи проверку для этого значения». С ним можно делать что угодно, и компилятор молчит. Это опасно: any заражает соседний код и сводит на нет всю пользу TypeScript — ошибки снова всплывают только в рантайме.
let data: any = fetchSomething();
data.foo.bar.baz; // ошибки не будет на сборке — упадёт в рантайме
const n: number = data; // тоже пропустит
unknown — безопасная альтернатива. Это тоже «неизвестно что», но компилятор не даёт ничего с таким значением сделать, пока ты не докажешь его тип проверкой.
let data: unknown = fetchSomething();
// data.foo; // ошибка: тип ещё неизвестен
if (typeof data === "string") {
data.toUpperCase(); // здесь уже безопасно: внутри проверки это string
}
Поэтому для внешних данных (тело запроса, ответ стороннего API) бери unknown, а не any, и проверяй тип перед использованием. Как именно сужать тип проверками — отдельная тема, см. ссылки ниже.
never — тип, у которого вообще нет значений. Он появляется там, где значения быть не может: функция, которая всегда бросает исключение, или ветка кода, до которой нельзя дойти.
function fail(message: string): never {
throw new Error(message);
}
Короткая формула: any — выключает типы (избегай), unknown — безопасный «пока не знаю», never — значения не бывает.
Коротко
- TypeScript добавляет к JavaScript проверку типов на этапе сборки; в рантайме типы стираются, внешние данные всё равно надо валидировать.
- Аннотация
: типзадаёт тип явно; вывод типов (inference) часто делает это за тебя — аннотируй границы функций, внутри доверяй выводу. interface— для формы объекта (расширяется, сливается);type— для union, пересечений, псевдонимов и всего остального.union(|) — «либо одно, либо другое»;intersection(&) — «всё сразу».- Литеральные типы в union задают набор допустимых значений;
?делает поле опциональным. anyотключает проверки и опасен;unknown— безопасная замена с обязательной проверкой;never— тип без значений.
Что почитать дальше
- Что нужно знать из JavaScript — база языка, поверх которой ложатся типы.
- Сужение типов и type guards — как безопасно работать с union и
unknown. - Дженерики (generics) — как описывать типы, работающие с любыми данными.