Введение в TypeScript

TypeScript — это язык программирования с открытым исходным кодом, разработанный компанией Microsoft. Это надмножество JavaScript, которое, будучи полностью совместимым с синтаксисом JavaScript, добавляет необязательную статическую систему типов и возможности объектно-ориентированного программирования на основе классов.

Код TypeScript в конечном итоге компилируется в чистый JavaScript и может выполняться в любой среде, поддерживающей JavaScript, включая браузеры, Node.js и мобильные платформы.


Почему TypeScript?

JavaScript — динамически типизированный язык; тип переменной можно определить только во время выполнения.

Эта гибкость хорошо работает в небольших проектах, но по мере роста масштаба проекта постепенно проявляются проблемы:

Сценарий проблемыДилемма JavaScriptРешение TypeScript
Передача неверного типа в параметр функцииОшибки появляются только во время выполнения, их сложно обнаружить заранееОшибки появляются во время компиляции, с подсказками IDE в реальном времени
Обращение к несуществующим свойствамВозвращаетundefined, поведение непредсказуемоКомпилятор сразу сообщает об ошибке и отказывается пропускать это
Рефакторинг больших проектовИзменишь в одном месте — неизвестно, что сломаетсяСистема типов автоматически отслеживает все ссылки
Командная работаКакие параметры принимает функция и что она возвращает, полностью зависит от комментариев или документацииСигнатуры типов служат документацией, с автодополнением в IDE
Читаемость кодаНельзя напрямую узнать структуру данных из определения функцииИнтерфейсы и псевдонимы типов делают структуру данных понятной с первого взгляда

Основная цель TypeScript — не заменить JavaScript, а сделать крупные проекты на JavaScript управляемыми и поддерживаемыми. Это «страховочная сеть» для JavaScript, а не конкурент.

Давайте рассмотрим самый наглядный пример:

Пример

// JavaScript: ошибка возникает только во время выполнения
function greet(name) {
    return "Hello, " + name.toUpperCase();
}

greet(123); // Ошибка во время выполнения: name.toUpperCase не является функцией

Пример

// TypeScript: ошибки можно обнаружить во время компиляции
function greet(name: string): string {
    return "Hello, " + name.toUpperCase();
}

greet(123); // Ошибка компиляции: Аргумент типа 'number' не может быть присвоен параметру типа 'string'
greet("example"); // Правильно: Hello, EXAMPLE

Связь между TypeScript и JavaScript

Связь между TypeScript и JavaScript можно выразить одной фразой:TypeScript — это надмножество JavaScript, что означает, что весь допустимый код JavaScript также является допустимым кодом TypeScript.

Иллюстрация отношений множеств

JavaScript (весь код JS)
⊂ TypeScript (JS + система типов + новые возможности)
⊂ Скомпилированный вывод (стандартный JavaScript, может запускаться в любой среде)

Основные различия между ними:

Пункт сравненияJavaScriptTypeScript
Система типовДинамическая типизация, определяется во время выполненияСтатическая типизация, проверяется во время компиляции (необязательно)
Способ выполненияЗапускается напрямую в браузере / Node.jsДолжен быть скомпилирован в JS перед запуском
Момент обнаружения ошибокВремя выполненияНа этапе компиляции (обнаруживаются рано)
Поддержка IDEБазовое автодополнениеМощный вывод типов, точное автодополнение, поддержка рефакторинга
Кривая обученияОтносительно пологаяТребуется дополнительное изучение системы типов
Совместимость с существующим JS—Полностью совместим, можно мигрировать постепенно
Расширение файла.js.tsили.tsx(включая JSX)

TypeScript поддерживает «постепенное внедрение»: не нужно переписывать весь проект сразу. Можно сначала.jsизменение.ts, затем постепенно добавлять типы в ключевые модули, а существующий код продолжит работать как обычно.


Основные возможности

TypeScript представляет полноценную систему типов поверх JavaScript. Ниже перечислены наиболее часто используемые основные возможности.

Базовые аннотации типов

Объявляйте типы после переменных, параметров функций и возвращаемых значений с помощью двоеточия; это самый базовый синтаксис TypeScript.

Пример

// Базовые типы: number, string, boolean, null, undefined, symbol, bigint
let age: number = 25;
let username: string = "example";
let isActive: boolean = true;

// Типы массивов, два эквивалентных способа
let scores: number[] = [90, 85, 92];
let tags: Array<string> = ["typescript", "javascript"];

// Кортеж: массив с фиксированной длиной и типами
let point: [number, number] = [10, 20];
let entry: [string, number] = ["EXAMPLE", 100];

// Функция: типы параметров + тип возвращаемого значения
function add(a: number, b: number): number {
    return a + b;
}

// void: функция не имеет возвращаемого значения
function log(msg: string): void {
    console.log(msg);
}

// Необязательный параметр: добавьте ? после имени параметра
function greet(name: string, title?: string): string {
    return title ? `${title} ${name}` : name;
}

console.log(greet("EXAMPLE"));          // Вывод: EXAMPLE
console.log(greet("example", "Mr."));  // Вывод: Mr. example

Интерфейс

Интерфейс используется для описания «формы» объекта, определяя, какие свойства и методы должны быть у объекта.

Пример

// Определить интерфейс: описать структуру объекта пользователя
interface User {
    id: number;          // Обязательно
    name: string;        // Обязательно
    email?: string;      // Необязательно, помечается знаком ?
    readonly role: string; // Только для чтения, нельзя изменить после присваивания
}

// Параметр функции использует тип интерфейса
function printUser(user: User): void {
    console.log(`ID: ${user.id}, Name: ${user.name}`);
    if (user.email) {
        console.log(`Email: ${user.email}`);
    }
}

const admin: User = {
    id: 1,
    name: "EXAMPLE",
    email: "[email protected]",
    role: "admin",
};

printUser(admin);
// Вывод: ID: 1, Имя: EXAMPLE
// Вывод: Эл. почта: [email protected]

// admin.role = "user"; // Ошибка: невозможно присвоить 'role', так как это свойство доступно только для чтения.

// Наследование интерфейсов
interface AdminUser extends User {
    permissions: string[];
}

Псевдоним типа

Псевдонимы типов используют ключевое словоtypeдля создания и могут определять псевдонимы для любого типа, включая сложные структуры, такие как объединённые типы и типы пересечения.

Пример

// Объединённый тип: переменная может быть одним из нескольких типов
type ID = string | number;

let userId: ID = "abc-123"; // Допустимо
userId = 456;               // Также допустимо

// Литеральный тип: ограничивает переменную конкретными значениями
type Direction = "up" | "down" | "left" | "right";
type Status = "pending" | "active" | "inactive";

function move(dir: Direction): void {
    console.log(`Moving ${dir}`);
}

move("up");    // Верно
// move("diagonal"); // Ошибка: не входит в диапазон литерала

// Тип пересечения: объединяет несколько типов
type WithTimestamp = {
    createdAt: Date;
    updatedAt: Date;
};

type UserRecord = User & WithTimestamp; // Имеет все свойства как User, так и WithTimestamp

// Псевдоним типа функции
type Transformer<T, U> = (input: T) => U;
const toNumber: Transformer<string, number> = (s) => parseInt(s, 10);

Обобщения

Дженерики позволяют писать переиспользуемые компоненты, сохраняя типобезопасность. Можно думать о дженериках как о "переменных типа" — передавайте конкретные типы при их использовании.

Пример

// Без дженериков: можно работать только с типом number
function firstNumber(arr: number[]): number {
    return arr[0];
}

// Использование дженериков: T — это параметр типа, конкретный тип определяется во время вызова
function first<T>(arr: T[]): T {
    return arr[0];
}

const n = first<number>([1, 2, 3]);    // n имеет тип number
const s = first<string>(["a", "b"]);  // s имеет тип string
const inferred = first([true, false]); // TypeScript автоматически выводит T как boolean

// Обобщённый интерфейс
interface ApiResponse<T> {
    data: T;
    status: number;
    message: string;
}

// Использование обобщённого интерфейса
const userResponse: ApiResponse<User> = {
    data: { id: 1, name: "EXAMPLE", role: "admin" },
    status: 200,
    message: "success",
};

// Обобщённое ограничение: T должен иметь свойство id
function getById<T extends { id: number }>(items: T[], id: number): T | undefined {
    return items.find(item => item.id === id);
}

Перечисления

Перечисления используются для определения набора именованных констант, делая "магические числа" или "магические строки" в коде осмысленными.

Пример

// Числовое перечисление (по умолчанию инкрементируется с 0)
enum Direction {
    Вверх,     // 0
    Down,   // 1
    По левому краю,   // 2
    Вправо,  // 3
}

console.log(Direction.Up);    // Вывод: 0
console.log(Direction[0]);    // Вывод: "Up" (обратное сопоставление)

// Строковое перечисление (рекомендуется, значения более удобочитаемы при отладке)
enum Color {
    Red = "RED",
    Green = "GREEN",
    Blue = "BLUE",
}

function paint(color: Color): void {
    console.log(`Рисование в ${color}`);
}

paint(Color.Red); // Вывод: Рисование в RED
// paint("red"); // Ошибка: строка "red" не имеет типа Color

// const enum: встраивается после компиляции, снижая накладные расходы во время выполнения
const enum HttpStatus {
    ОК = 200,
    NotFound = 404,
    InternalError = 500,
}

const status: HttpStatus = HttpStatus.OK; // После компиляции напрямую заменяется числом 200

Вывод типов

TypeScript не требует вручную аннотировать типы для каждой переменной; компилятор автоматически выводит их на основе присвоенного значения.

Пример

// TypeScript автоматически выводит типы переменных, явная аннотация не требуется
let count = 0;            // Выводится как number
let name = "EXAMPLE";      // Выводится как string
let flag = true;          // Выводится как boolean

// Выведенный тип элемента массива
let numbers = [1, 2, 3];  // Выводится как number[]

// Вывод типа возвращаемого значения функции
function double(n: number) {  // Тип возвращаемого значения автоматически выводится как number
    return n * 2;
}

// Вывод типа объектного литерала
const config = {
    host: "localhost",    // string
    port: 3000,           // number
    debug: false,         // boolean
};

// config.port = "3000"; // Ошибка: тип 'string' не может быть присвоен типу 'number'.

Практический совет: параметрам функции обычно нужны явные аннотации типов (потому что вызывающий код не может вывести типы из значений параметров), тогда как локальные переменные и возвращаемые значения внутри тела функции в основном могут полагаться на выведение типов — не нужно вручную аннотировать всё.

Классы и модификаторы доступа

TypeScript полностью поддерживает синтаксис классов ES6 и добавляетpublic、private、protected、readonlyчетыре модификатора доступа.

Пример

class Animal {
    readonly name: string;         // Только для чтения, нельзя изменить после инициализации
    private age: number;           // Приватный, доступен только внутри класса
    protected species: string;     // Защищённый, доступен подклассам

    constructor(name: string, age: number, species: string) {
        this.name = name;
        this.age = age;
        this.species = species;
    }

    // публичный метод (по умолчанию public, можно опустить)
    public introduce(): string {
        return `IЯ ${this.name}, ${this.species}.`;
    }

// геттер: вызвать метод, как при доступе к свойству
    get info(): string {
        return `${this.name} (${this.age} лет)`;
    }
}

class Dog extends Animal {
    private breed: string;

    constructor(name: string, age: number, breed: string) {
super(name, age, "Canis lupus familiaris"); // Вызов конструктора родительского класса
        this.breed = breed;
    }

// Подкласс может обращаться к protected-свойствам
    describe(): string {
        return `${this.name} — это ${this.species}, порода: ${this.breed}`;
    }
}

const dog = new Dog("ПРИМЕР", 3, "Лабрадор");
console.log(dog.introduce()); // Вывод: I'
m EXAMPLE, a Canis lupus familiaris.
console.log(dog.info);         // Вывод: EXAMPLE (3 года)
// console.log(dog.age); // Ошибка: к приватному свойству нельзя получить доступ извне

Декоратор

Декораторы — это синтаксис метапрограммирования, используемый для добавления дополнительного поведения классам, методам и свойствам; часто встречается в таких фреймворках, как Angular, NestJS и т. д.

TypeScript 5.0 реализовал декораторы как официальный стандарт (предложение Stage 3), так что больше не требуетсяexperimentalDecoratorsфлаг.

Пример

// Декоратор класса: выполняется при определении класса
function sealed(constructor: Function) {
    Object.seal(constructor);         // Предотвращает добавление новых свойств
    Object.seal(constructor.prototype);
}

// Декоратор метода: может использоваться для логирования, проверки прав доступа и т. д.
function log(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    const original = descriptor.value;
    descriptor.value = function (...args: any[]) {
        console.log(`Вызов ${propertyKey} с аргументами:`, args);
        const result = original.apply(this, args);
        console.log(`${propertyKey} вернул:`, result);
        return result;
    };
}

@sealed
class Calculator {
    @log
    add(a: number, b: number): number {
        return a + b;
    }
}

const calc = new Calculator();
calc.add(1, 2);
// Вывод: Вызов add с аргументами: [ 1, 2 ]
// Вывод: add вернул: 3

Области применения

Благодаря полной совместимости с JavaScript, TypeScript можно использовать почти везде, где работает JavaScript, и он приносит дополнительные преимущества в крупных проектах.

Фронтенд-разработка

Это наиболее широко используемый сценарий для TypeScript. Все три основных фронтенд-фреймворка обеспечивают первоклассную поддержку TypeScript:

ФреймворкПоддержка TypeScriptТипичный сценарий
AngularОфициальный язык, TypeScript по умолчанию, не избежать.Корпоративные SPA, административные системы
ReactОфициально предоставляется@types/react, Create React App и Vite оба включают шаблоны TypeScriptЭлектронная коммерция, контент-платформы, административные/бэкенд-приложения
VueИсходный код ядра Vue 3 переписан на TypeScript, а Composition API значительно повышает удобство работы с TypeScriptНебольшие и средние проекты, постепенная миграция
Next.js / NuxtВстроенная поддержка TypeScript, включена по умолчанию для новых проектовПолностековые SSR/SSG-приложения

Разработка бэкенда на Node.js

TypeScript столь же распространён в разработке бэкенда на Node.js.NestJSВ настоящее время это самый популярный бэкенд-фреймворк на TypeScript, использующий модульную архитектуру, похожую на Angular, со встроенной поддержкой внедрения зависимостей и декораторов.

Кроме того,DenoСреда выполнения с самого начала нативно поддерживает TypeScript и может запускать его напрямую без какой-либо конфигурации..tsфайлы.BunТакже имеет встроенную поддержку TypeScript.

Инструменты и скрипты командной строки

С помощьюts-node、tsxи других инструментов код TypeScript можно выполнять напрямую без предварительной компиляции, что делает его идеальным для написания сборочных скриптов и инструментов CLI.

Мобильная разработка и кроссплатформенность

React NativeПолностью поддерживает TypeScript, и многие корпоративные мобильные приложения (такие как Microsoft Office mobile, Shopify и т. д.) используют этот технологический стек.ExpoШаблон нового проекта также по умолчанию основан на TypeScript.

Разработка игр и графика

Babylon.js(3D-движок Microsoft) полностью написан на TypeScript и рассматривает определения типов TypeScript как полноправные элементы первого класса.Phaserи другие 2D-игровые движки также предоставляют полные определения типов.


История разработки

Появление и развитие TypeScript тесно связаны с эволюцией экосистемы JavaScript, и каждый выпуск крупной версии обусловлен ясными инженерными потребностями.

Истоки (2010 - 2012)

История TypeScript началась с инженерной задачи внутри Microsoft: как сделать крупные проекты на JavaScript сопровождаемыми.

ДизайнерAnders Hejlsberg— легенда индустрии, главный архитектор C# и создатель Turbo Pascal и Delphi. Он начал проектировать этот язык около 2010 года, с основной целью внедрить статическую проверку типов, не нарушая совместимость с JavaScript.

Ещё одной предпосылкой к выпуску TypeScript от Microsoft стал выход Windows 8 — приложения можно было разрабатывать с использованием HTML + JavaScript, и Microsoft надеялась привлечь программистов .NET, поэтому многие особенности синтаксиса TypeScript были намеренно сделаны похожими на C# и .NET.

В октябре 2012 года TypeScript 0.8 был публично выпущен после примерно двух лет внутренней разработки в Microsoft.

Ранние версии (2013 - 2015)

ВремяVersionВажные события
2013-060.9Официальный стабильный релиз, значительно улучшенная производительность, улучшенная интеграция с IDE
2014-041.0Первый официальный стабильный выпуск, в котором появились классы, интерфейсы, модули и поддержка компиляции в стандартный JavaScript
2014-07—Исходный код компилятора TypeScript открыт, размещён на GitHub, принимаются вклады сообщества
2015-04—Microsoft выпускает Visual Studio Code с глубокой встроенной поддержкой TypeScript; эти два продукта дополняют друг друга
2015-071.5Добавлена поддержка синтаксиса модулей ES6, декораторов (экспериментальных) и пространств имён

Быстрый рост (2016 - 2019)

Этот этап стал критически важным периодом для выхода TypeScript из нишевого инструмента в мейнстрим, а главной движущей силой стали решения в экосистеме фреймворков.

ВремяВерсия / СобытиеЗначимость вехи
2016-09TypeScript 2.0Были введены non-nullable-типы (--strictNullChecks), одна из важнейших возможностей типобезопасности TypeScript; введены размеченные объединённые типы
2016Выпущен Angular 2Команда Google Angular объявила о принятии TypeScript в качестве официального языка разработки, выведя TypeScript в центр общественного внимания
2017Серия TypeScript 2.xПоследовательно были введены условные типы, сопоставленные типы и ключевое слово infer, что значительно повысило выразительность системы типов
2018-07TypeScript 3.0Project References, поддерживающие большие монорепозитории; был введён тип unknown (более безопасный верхний тип, чем any)
2019DefinitelyTypedСопровождаемый сообществом репозиторий определений типов DefinitelyTyped (@types/*) охватывает более 7,000 пакетов, практически покрывая все основные npm-библиотеки

Зрелость и внедрение (2020 - 2022)

ВремяВерсия / СобытиеВажный контент
2020-08TypeScript 4.0Вариативные кортежные типы, помеченные элементы кортежей; значительно улучшенный опыт работы в редакторе
2021Серия TypeScript 4.xТипы литералов шаблонов, улучшенные встроенные служебные типы,noImplicitOverrideи другие улучшения строгого режима
2022Опрос State of JSУровень использования TypeScript впервые превзошёл чистый JavaScript, став самым популярным языком-надмножеством в экосистеме JS
2022Внедрение Vue 3 / ViteЯдро Vue 3 переписано на TypeScript; в сочетании с Vite это выводит опыт работы с TypeScript во фронтенд-разработке на новый уровень

Современная эпоха (2023 — настоящее время)

ВремяVersionКлючевые особенности
2023-03TypeScript 5.0Современные декораторы (стандарт Stage 3), параметры типа const, все перечисления обновлены до union-перечислений, со значительными улучшениями производительности
2023-08TypeScript 5.2Введено явное управление ресурсами (usingключевое слово), решая проблему детерминированной очистки таких ресурсов, как подключения к базам данных и файловые дескрипторы
2024-03TypeScript 5.4Улучшено сужение типов в замыканиях,NoInferслужебные типы
2024-06TypeScript 5.5вывод предикатов типа, проверка синтаксиса регулярных выражений, что позволяет системе типов точнее понимать логику кода
2024-11TypeScript 5.7Поддержка--target es2024, более надёжное обнаружение неинициализированных переменных
2025-03TypeScript 5.8Улучшенная проверка возвращаемых значений для условных типов и типов доступа по индексу; поддерживает при--module nodenextчерезrequire()загрузке ESM-модулей (требуется Node.js 22+)

Перспективы на будущее: переписывание компилятора на Go

В начале 2025 года Microsoft объявила о важном решении: переписать компилятор TypeScript на Go, кодовое название проекта —Corsa。

В официальном заявлении говорится, что переписанный компилятор, как ожидается, обеспечит повышение производительности более чем в 10 раз, сократив время сборки крупных проектов с минут до секунд.

Согласно плану, версия на Go будет официально выпущена в TypeScript 7.x, тогда как текущие версии TypeScript 5.x / 6.x продолжат обычную итерацию и поддержку. Ожидается, что переписывание завершится к концу 2025 года.

Это переписывание не изменит синтаксис или систему типов самого языка TypeScript; для разработчиков это прозрачное обновление — ваш код не нуждается в изменениях, только скорость компиляции значительно повышается.


Сравнение TypeScript с другими статически типизированными языками

Если у вас есть опыт работы с другими строго типизированными языками, следующее сравнение поможет вам быстро понять философию дизайна TypeScript.

Пункт сравненияTypeScriptJava / C#Go
Система типовСтруктурная типизация (утиная типизация)Номинальная типизация (необходимо явно объявлять отношения наследования)Структурная типизация (неявные интерфейсы)
Null-безопасностьВключениеstrictNullChecksподдерживает это в дальнейшемJava требует аннотаций или Optional, C# 8+ поддерживаетЧерез значения ошибок и проверки на nil
ОбобщенияПоддерживается, синтаксис похож на Java/C#Поддерживается, в Java есть ограничения из-за стирания типовGo 1.18+ поддерживает это, с более простым синтаксисом
Скомпилированный выводJavaScript (выполняется в JS-средах)Байт-код (выполняется на JVM / CLR)Нативный машинный код
Проверка типов во время выполненияНет (информация о типах стирается после компиляции)Да (механизм рефлексии)Отсутствует
Кривая обученияДружелюбен к JS-разработчикам, можно внедрять постепенноБолее сложен для новичковОтносительно прост, умеренная кривая обучения

TypeScript используетсистему структурной типизации(Structural Typing): если два типа имеют одинаковую «форму», они считаются совместимыми, без необходимости явно объявлять отношения наследования. Это принципиально отличается от номинальной типизации Java/C# и также является ключевой причиной, по которой TypeScript может бесшовно взаимодействовать с JavaScript.


Ограничения TypeScript

TypeScript — не серебряная пуля; понимание его границ столь же важно.

ОграничениеОписаниеКак решить
Нет типов во время выполненияИнформация о типах полностью стирается после компиляции, и на типы нельзя полагаться при проверках во время выполненияИспользуйте библиотеки, такие какzod、io-tsкогда требуется проверка во время выполнения
Этап компиляцииДобавляет этап компиляции по сравнению с чистым JS, увеличивая сложность проектаСовременные инструменты сборки (Vite, esbuild) уже хорошо с этим справляются
any как средство обхода проверки типовЧрезмерное использованиеanyделает проверку типов неэффективнойВключитьnoImplicitAnyиstrictрежим в сочетании с правилами ESLint
Барьер гимнастики типовСложные условные типы и mapped types не подходят для начинающихБольшинству бизнес-кода не нужны продвинутые типы; двигайтесь шаг за шагом
Поддержка сторонних библиотекНесколько старых библиотек не имеют определений типов, и вам придётся самостоятельно писать .d.tsСначала проверьте,@types/*; если их действительно нет, напишите файл объявления вручную
Другие расширения