LEARN X · ЗА 15 МИН
TypeScript
TypeScript за 15 минут: типы, интерфейсы, дженерики, union, narrowing, классы и утилитные типы — экспресс-тур для тех, кто знает JavaScript.
TypeScript — это JavaScript со статическими типами. Тот же синтаксис, та же среда выполнения, но компилятор ловит ошибки до запуска. Этот тур предполагает, что вы знаете JS, и показывает только то, что добавляет TypeScript. Весь язык — в комментариях кода ниже.
1. Зачем TypeScript и базовые типы
Типы пишутся после двоеточия. Компилятор выводит их сам, но аннотации делают код самодокументируемым.
// Аннотация типа: имя: Тип
let username: string = "Ада";
let age: number = 36; // одно число и для int, и для float
let isAdmin: boolean = false;
// Вывод типов (inference): TS сам понимает тип из значения
let city = "Лондон"; // city: string автоматически
// city = 42; // Ошибка: number не присваивается string
// Спецтипы значений
let nothing: null = null;
let missing: undefined = undefined;
let huge: bigint = 9007199254740993n;
let sym: symbol = Symbol("id");
// Шаблонные строки и операторы — как в JS, но с проверкой типов
const greeting: string = `Привет, ${username}, тебе ${age}`;
// Компилируется командой: tsc file.ts -> file.js (типы стираются)
2. Массивы и кортежи
// Массив: Тип[] или Array<Тип>
let nums: number[] = [1, 2, 3];
let names: Array<string> = ["Ани", "Боб"];
nums.push(4);
// nums.push("пять"); // Ошибка: ожидался number
// Массив с несколькими типами через union
let mixed: (number | string)[] = [1, "два", 3];
// Кортеж (tuple): фиксированная длина и тип каждой позиции
let point: [number, number] = [10, 20];
let pair: [string, number] = ["возраст", 36];
// pair = [36, "возраст"]; // Ошибка: порядок типов нарушен
// Именованные кортежи (для читаемости)
let rgb: [red: number, green: number, blue: number] = [255, 128, 0];
// Readonly-кортеж нельзя мутировать
const origin: readonly [number, number] = [0, 0];
// origin[0] = 5; // Ошибка: только для чтения
3. Объекты и интерфейсы
interface описывает форму объекта.
interface User {
id: number;
name: string;
email?: string; // ? — поле опционально (может отсутствовать)
readonly createdAt: Date; // readonly — задаётся один раз
}
const u: User = {
id: 1,
name: "Ада",
createdAt: new Date(),
// email можно не указывать
};
// u.createdAt = new Date(); // Ошибка: readonly
// Интерфейсы расширяются через extends
interface Admin extends User {
role: "admin";
}
// Index signature — произвольные ключи известного типа
interface Dictionary {
[key: string]: number;
}
const scores: Dictionary = { math: 5, history: 4 };
4. Type aliases и объединения
// type — псевдоним для любого типа (не только объекта)
type ID = number | string; // union: число ИЛИ строка
let userId: ID = 42;
userId = "abc-42"; // тоже валидно
// Литеральные типы: значение само становится типом
type Direction = "north" | "south" | "east" | "west";
let dir: Direction = "north";
// dir = "up"; // Ошибка: не входит в union
// type умеет описывать объекты (как interface)
type Point = { x: number; y: number };
// Пересечение типов через & (объединяет поля)
type Named = { name: string };
type Aged = { age: number };
type Person = Named & Aged; // { name: string; age: number }
// interface vs type: interface расширяется и сливается,
// type гибче (union, кортежи, mapped-типы). Для объектов — на вкус.
5. Функции
// Типы параметров и возвращаемого значения
function add(a: number, b: number): number {
return a + b;
}
// Опциональный (?) и параметр по умолчанию
function greet(name: string, greeting: string = "Привет"): string {
return `${greeting}, ${name}`;
}
function log(msg: string, level?: string): void {
// void — функция ничего не возвращает
console.log(level ?? "info", msg);
}
// Rest-параметры
function sum(...nums: number[]): number {
return nums.reduce((a, b) => a + b, 0);
}
// Тип функции как значение
const multiply: (a: number, b: number) => number = (a, b) => a * b;
// Перегрузки (overloads): несколько сигнатур + одна реализация
function parse(x: string): string[];
function parse(x: number): number;
function parse(x: string | number): string[] | number {
return typeof x === "string" ? x.split("") : x * 2;
}
6. Enum и литеральные типы
// Числовой enum (по умолчанию 0, 1, 2...)
enum Status {
Active, // 0
Inactive, // 1
Banned, // 2
}
let s: Status = Status.Active;
// Строковый enum (явные значения — читается в рантайме)
enum Color {
Red = "RED",
Green = "GREEN",
Blue = "BLUE",
}
// const enum — инлайнится компилятором, нет объекта в рантайме
const enum Size { S, M, L }
let sz = Size.M; // подставится число 1
// Часто union литералов лучше enum: легче и tree-shakeable
type Theme = "light" | "dark";
let theme: Theme = "dark";
7. Дженерики
Дженерики — параметры типа <T>: код работает с любым типом, сохраняя проверки.
// T — переменная типа, подставляется при вызове
function identity<T>(value: T): T {
return value;
}
const a = identity<string>("hi"); // T = string
const b = identity(42); // T = number (выведен сам)
// Дженерик-интерфейс
interface Box<T> {
value: T;
}
const boxed: Box<number> = { value: 10 };
// Ограничение extends: T обязан иметь поле length
function longest<T extends { length: number }>(x: T, y: T): T {
return x.length >= y.length ? x : y;
}
longest("abc", "de"); // строки ок (есть length)
longest([1, 2], [3]); // массивы ок
// longest(1, 2); // Ошибка: у number нет length
// Несколько параметров типа
function pair<K, V>(key: K, value: V): [K, V] {
return [key, value];
}
8. Сужение типов (narrowing)
Внутри union TS сужает тип по проверкам — и вы получаете точные подсказки.
// typeof — для примитивов
function format(x: string | number): string {
if (typeof x === "string") {
return x.toUpperCase(); // здесь x: string
}
return x.toFixed(2); // здесь x: number
}
// in — проверка наличия поля
type Cat = { meow: () => void };
type Dog = { bark: () => void };
function speak(pet: Cat | Dog): void {
if ("meow" in pet) pet.meow();
else pet.bark();
}
// instanceof — для классов
function len(x: Date | string): number {
return x instanceof Date ? x.getFullYear() : x.length;
}
// Пользовательский type guard: возвращаемый тип `arg is Type`
function isCat(pet: Cat | Dog): pet is Cat {
return "meow" in pet;
}
if (isCat({ meow: () => {} })) { /* тип сужен до Cat */ }
9. Классы
interface Greetable {
greet(): string; // контракт, который класс реализует
}
class Account implements Greetable {
public name: string; // доступно везде (по умолчанию)
private balance: number; // только внутри класса
protected pin: number; // класс + наследники
readonly id: number; // нельзя менять после создания
// Краткая запись: модификатор в конструкторе создаёт поле
constructor(name: string, balance: number, public currency: string = "RUB") {
this.name = name;
this.balance = balance;
this.pin = 0;
this.id = Math.random();
}
greet(): string {
return `Счёт ${this.name}: ${this.balance} ${this.currency}`;
}
// Геттер
get info(): string {
return this.greet();
}
}
class Savings extends Account {
withdraw(): void {
// this.balance; // Ошибка: private недоступен
console.log(this.pin); // protected — можно
}
}
10. Утилитные типы
Встроенные дженерики, которые трансформируют существующие типы.
interface Todo {
id: number;
title: string;
done: boolean;
}
// Partial<T> — все поля опциональны (удобно для обновлений)
function update(todo: Todo, patch: Partial<Todo>): Todo {
return { ...todo, ...patch };
}
// Pick<T, K> — оставить только указанные поля
type TodoPreview = Pick<Todo, "id" | "title">;
// Omit<T, K> — убрать указанные поля
type TodoBody = Omit<Todo, "id">; // { title; done }
// Record<K, V> — объект с ключами K и значениями V
type Pages = Record<string, number>;
const pages: Pages = { home: 1, about: 2 };
// Readonly<T> — все поля только для чтения
const frozen: Readonly<Todo> = { id: 1, title: "x", done: false };
// frozen.done = true; // Ошибка
// Прочие: Required<T>, ReturnType<F>, Parameters<F>, Awaited<P>
11. any / unknown / never и null-безопасность
// any — отключает проверки (избегайте: теряете весь смысл TS)
let loose: any = 5;
loose.foo.bar(); // компилятор молчит — опасно
// unknown — безопасная альтернатива: сначала сузь тип
let safe: unknown = JSON.parse("{}");
// safe.foo; // Ошибка: нужно проверить тип
if (typeof safe === "string") safe.toUpperCase();
// never — значение, которого не бывает (исчерпывающие проверки)
function fail(msg: string): never {
throw new Error(msg); // функция не возвращает управление
}
// strictNullChecks: null/undefined не входят в типы по умолчанию.
// Нужно объявить их явно через union:
function find(arr: string[], q: string): string | undefined {
return arr.find((x) => x === q);
}
const res = find(["a"], "b");
// res.length; // Ошибка: res может быть undefined
// Опциональная цепочка ?. и ?? (nullish coalescing)
const n = res?.length ?? 0; // если undefined -> 0
// Non-null assertion ! — "я уверен, что не null" (на свой риск)
const sure = find(["a"], "a")!.length;
12. Дженерик-утилиты на практике
Типизация ответа API — где дженерики реально окупаются.
// Обёртка ответа: T — тип полезных данных
interface ApiResponse<T> {
data: T;
status: number;
error: string | null;
}
interface User {
id: number;
name: string;
}
// fetch с типизацией: возвращает Promise нужной формы
async function getJson<T>(url: string): Promise<ApiResponse<T>> {
const res = await fetch(url);
const data = (await res.json()) as T; // приведение типа через as
return { data, status: res.status, error: null };
}
// Вызов: подставляем конкретный тип — дальше всё проверено
async function main(): Promise<void> {
const r = await getJson<User[]>("/api/users");
r.data.forEach((u) => console.log(u.name)); // u: User — есть .name
const single = await getJson<User>("/api/users/1");
console.log(single.data.id); // .id типизирован
}
// Итог: один дженерик ApiResponse<T> даёт точные типы для
// любого эндпоинта без дублирования кода. В этом сила TypeScript.