Антон Ларичев

Введение
TypeScript — это надстройка над JavaScript, которая добавляет статическую типизацию. Если вы уже пишете на JS, освоить базовый TypeScript можно буквально за день. В этой статье разберём три кита языка: типы, интерфейсы и дженерики. После прочтения вы сможете уверенно читать и писать типизированный код.
Главная польза TypeScript — компилятор находит ошибки до запуска программы. Опечатка в имени поля, передача строки вместо числа, обращение к несуществующему методу — всё это будет подсвечено в редакторе.
Базовые типы
Начнём с примитивов. В TypeScript есть string, number, boolean, null, undefined, а также any и unknown.
// Явная аннотация типа
let userName: string = "Анна";
let age: number = 25;
let isActive: boolean = true;
// Вывод типа — компилятор сам поймёт, что это string
let city = "Москва";
// Массивы
let scores: number[] = [10, 20, 30];
let tags: Array<string> = ["ts", "js"];
// Кортеж — фиксированная длина и порядок типов
let point: [number, number] = [10, 20];
Тип any отключает проверки и должен использоваться только в крайних случаях. Если тип неизвестен — берите unknown: он безопаснее, потому что заставляет проверять значение перед использованием.
function parseInput(value: unknown): string {
// Сначала сужаем тип через typeof
if (typeof value === "string") {
return value.toUpperCase();
}
return String(value);
}
Union и Literal типы
Union-тип позволяет переменной принимать одно из нескольких значений. Literal-тип ограничивает конкретными литералами.
// Может быть либо строкой, либо числом
let id: string | number = "abc-123";
id = 42;
// Только эти три значения и ничего больше
type Status = "pending" | "success" | "error";
let orderStatus: Status = "pending";
Интерфейсы
Интерфейс описывает форму объекта. Это контракт: какие поля обязательны, какие опциональны, какие методы должны быть.
interface User {
id: number;
name: string;
email: string;
// Опциональное поле — может отсутствовать
age?: number;
// Только для чтения — нельзя переназначить после создания
readonly createdAt: Date;
}
const user: User = {
id: 1,
name: "Иван",
email: "ivan@example.com",
createdAt: new Date(),
};
Интерфейсы можно расширять через extends. Это удобно для иерархий моделей.
interface Admin extends User {
permissions: string[];
}
const admin: Admin = {
id: 2,
name: "Мария",
email: "maria@example.com",
createdAt: new Date(),
permissions: ["read", "write", "delete"],
};
Интерфейс или type alias
Часто новички путаются между interface и type. На практике для описания объектов они почти взаимозаменяемы. interface лучше для публичных API библиотек — его можно расширять декларативно. type гибче: позволяет создавать union, intersection и условные типы.
// Через type — описываем то же самое
type Product = {
id: number;
title: string;
price: number;
};
// Композиция через intersection
type DiscountedProduct = Product & { discount: number };
Дженерики
Дженерики — это параметризованные типы. Они позволяют писать функции и классы, которые работают с разными типами, не теряя типобезопасности.
Представьте функцию, которая возвращает первый элемент массива. Без дженериков пришлось бы либо писать копии для каждого типа, либо использовать any и терять подсказки.
// T — параметр типа, подставится при вызове
function first<T>(items: T[]): T | undefined {
return items[0];
}
// Компилятор сам выведет: T = number
const num = first([1, 2, 3]); // тип number | undefined
// А здесь T = string
const word = first(["a", "b", "c"]); // тип string | undefined
Дженерики часто используют в обёртках над API-ответами.
interface ApiResponse<T> {
data: T;
status: number;
message: string;
}
// Конкретизируем дженерик при использовании
async function fetchUser(id: number): Promise<ApiResponse<User>> {
const response = await fetch(`/api/users/${id}`);
return response.json();
}
Ограничения дженериков
Иногда нужно гарантировать, что тип имеет определённое свойство. Для этого используется extends.
// T должен иметь поле length
function logLength<T extends { length: number }>(value: T): T {
console.log("Длина:", value.length);
return value;
}
logLength("hello"); // строка имеет length
logLength([1, 2, 3]); // массив тоже
// logLength(42); // ошибка: у числа нет length
Частые ошибки
Злоупотребление any. Самая распространённая беда новичков. Стоит поставить any в одном месте — и типизация рушится по всей цепочке вызовов. Если совсем непонятно, какой тип — используйте unknown и сужайте его проверками.
Игнорирование strict режима. В tsconfig.json обязательно включайте "strict": true. Без этого половина пользы от TypeScript теряется: null и undefined не отслеживаются, неявный any пропускается.
Путаница между типом и значением. Интерфейсы и типы существуют только на этапе компиляции и пропадают в JS. Нельзя проверить value instanceof MyInterface — этого интерфейса в рантайме нет.
Чрезмерное усложнение дженериков. Не превращайте сигнатуры функций в ребус из пяти параметров типа. Если читать тяжело — упростите, разбейте на промежуточные типы или откажитесь от дженерика там, где достаточно union.
Дублирование интерфейсов. Если две модели отличаются одним полем — расширяйте через extends или Pick/Omit, а не копируйте описание целиком.
Заключение
За день можно усвоить базу TypeScript: примитивные типы, union и literal, интерфейсы с расширением, дженерики с ограничениями. Этого достаточно, чтобы читать чужой код и писать свой без any. Дальше осваивайте утилитарные типы — Partial, Required, Pick, Omit, Record — и условные типы. Они открывают по-настоящему мощные приёмы метапрограммирования. Главное — пишите код в строгом режиме и не сдавайтесь, когда компилятор ругается. Каждая его ошибка — это потенциальный баг, который вы поймали до продакшена.






Комментарии
0