5.01. TypeScript
TypeScript

TypeScript — это язык программирования, разработанный компанией Microsoft. Он представляет собой строго типизированное надмножество языка JavaScript, которое компилируется в обычный JavaScript. Его основная цель — повысить надежность, масштабируемость и поддерживаемость кода, особенно в крупных и сложных приложениях, где JavaScript сам по себе может стать источником трудноуловимых ошибок.
История и разработка
TypeScript был разработан в Microsoft под руководством Андерса Хейльсберга, известного также как создатель языков Delphi и C#. Первый публичный релиз состоялся в октябре 2012 года. Он был создан как ответ на растущую сложность и масштабы JavaScript-приложений, особенно на стороне клиента (браузера) и сервера (Node.js). В отличие от чистого JavaScript, TypeScript предлагает статическую типизацию и другие инструменты, характерные для более традиционных языков программирования, таких как C# или Java, что позволяет разработчикам строить более надежные и понятные системы. TypeScript является проектом с открытым исходным кодом (open source), что означает, что его исходный код доступен для изучения, модификации и участия в разработке со стороны сообщества. Microsoft, несмотря на то, что инициировала проект, активно сотрудничает с внешними разработчиками и поддерживает его развитие. Это делает TypeScript не только продуктом одной компании, но и результатом усилий международного сообщества.
TypeScript как надмножество JavaScript
Термин "надмножество" означает, что TypeScript включает в себя весь синтаксис и функциональность JavaScript и добавляет к ним свои собственные возможности. Любой действительный код на JavaScript также является действительным кодом на TypeScript. Это позволяет разработчикам постепенно внедрять TypeScript в уже существующие JavaScript-проекты без необходимости переписывать всё с нуля. Основное нововведение TypeScript — это статическая типизация. Это значит, что типы переменных, параметров функций, возвращаемых значений и других элементов кода можно (и рекомендуется) указывать при написании кода. Компилятор TypeScript проверяет соответствие типов до выполнения программы, что позволяет выявить потенциальные ошибки на этапе компиляции, а не в процессе выполнения, как это часто бывает в JavaScript.
Проблемы JavaScript и зачем нужен TypeScript
JavaScript — это динамически типизированный язык. Это означает, что тип переменной определяется в момент присвоения значения, и может меняться в процессе выполнения программы. Это придает языку гибкость, но одновременно создает ряд проблем:
- Отсутствие Type Safety (безопасности типов): Невозможно гарантировать, что переменная будет содержать ожидаемый тип данных. Это может привести к ошибкам во время выполнения.
- Динамические типы: Поскольку типы определяются во время выполнения, среда выполнения должна выполнять дополнительную работу для определения типа и выполнения операций, что потенциально может сказаться на производительности.
- Отсутствие внятного автодополнения: IDE (интегрированная среда разработки) может испытывать трудности с предоставлением точных подсказок по автодополнению, особенно для сложных объектов, если структура неочевидна из кода.
- Невозможность рефакторинга: Переименование свойства объекта или параметра функции может быть трудоемким и рискованным процессом, так как нет гарантии, что все места использования будут корректно обновлены, особенно если код не покрыт тестами.
- Невозможность понять структуры данных: В больших проектах бывает сложно быстро разобраться в структуре передаваемых между функциями или классами данных.
Статическая типизация и её важность
Статическая типизация — это система, при которой типы переменных и выражений проверяются на этапе компиляции (до выполнения). Это контрастирует с динамической типизацией, где проверка происходит во время выполнения. Важность статической типизации заключается в следующем:
- Раннее обнаружение ошибок: Множество ошибок, связанных с несовместимостью типов (например, попытка вызвать метод строки на числе), выявляются еще до запуска программы.
- Повышение надежности кода: Благодаря проверкам типов, код становится более предсказуемым и устойчивым к опечаткам и логическим ошибкам.
- Улучшение поддержки IDE: Компилятор и IDE могут использовать информацию о типах для предоставления более точного автодополнения, навигации по коду, поиска ссылок и безопасного рефакторинга.
- Самодокументирование кода: Явное указание типов делает код более понятным и документированным, позволяя другим разработчикам быстрее понять, какие данные ожидаются и возвращаются функциями и методами.
Утиная типизация в JavaScript и структурная типизация в TypeScript
- Утиная типизация (Duck Typing): Это концепция, часто ассоциируемая с динамически типизированными языками, включая JavaScript. Принцип гласит: "Если это выглядит как утка, плавает как утка и крякает как утка, то это, вероятно, утка". В контексте программирования это означает, что объект считается совместимым с определённым "интерфейсом", если он имеет все требуемые свойства и методы, независимо от его реального типа или наследования.
- Структурная типизация (Structural Typing): Это система, принятая в TypeScript. Совместимость типов определяется структурой типа (его полями и методами), а не его именем (номинальная типизация). Это позволяет использовать утиную типизацию, но на этапе компиляции, обеспечивая безопасность типов. TypeScript проверяет, содержит ли один тип все члены другого типа, и если да, считает их совместимыми. Это делает TypeScript гибким, как JavaScript, но при этом безопасным, как статически типизированные языки.
Проектирование типов, принцип надежности, закон Лисков (LSP)
- Проектирование типов: Это процесс создания системы типов, которая точно моделирует доменную область приложения. Хорошо спроектированные типы помогают выразить намерения разработчика, ограничить возможные состояния данных и сделать ошибки невозможными или легкоуловимыми. Это включает в себя использование интерфейсов, объединений типов, литеральных типов и других инструментов TypeScript.
- Принцип надежности: В контексте TypeScript, это означает создание кода, который ведет себя предсказуемо и не вызывает ошибок времени выполнения из-за проблем с типами. Статическая типизация — ключевой инструмент для достижения этой надежности.
- Закон подстановки Барбары Лисков (LSP): Один из принципов SOLID. Он гласит, что объекты подтипа должны быть взаимозаменяемы с объектами их базового типа. В TypeScript это связано с совместимостью типов и наследованием. Если класс B наследует от класса A, то экземпляр B можно использовать везде, где ожидается A, без нарушения корректности программы.
Типобезопасность (Type Safety)
Типобезопасность — это свойство системы типов, которое предотвращает ошибки, связанные с неправильным использованием типов данных. В типобезопасном языке, как TypeScript, компилятор (или интерпретатор) гарантирует, что операции выполняются только над значениями подходящих типов. Это исключает такие ошибки, как попытка вызвать несуществующий метод, сложить строку с числом неявно, или передать неправильный тип в функцию. TypeScript достигает типобезопасности за счет строгой проверки типов на этапе компиляции.
Типы и значения
- Тип: Это множество допустимых значений и набор операций, которые можно с ними выполнить. Например, тип
numberвключает в себя все числовые значения (целые, дробные, положительные, отрицательные, специальные значенияNaN,Infinity) и операции, такие как сложение (+), вычитание (-), вызов методов (toFixed). - Значение: Это конкретный экземпляр данных. Например,
42,"hello",true— это конкретные значения.42является значением типаnumber,"hello"— значением типаstring,true— значением типаboolean.
TypeScript и Node.js
TypeScript не обязательно требует Node.js для своей работы, но Node.js часто используется в качестве среды выполнения для компилятора TypeScript (tsc) и для запуска скомпилированного JavaScript-кода на сервере. Компилятор TypeScript (tsc) сам по себе является приложением Node.js. Поэтому, чтобы установить и использовать tsc, обычно требуется установленный Node.js. Однако, TypeScript-код может быть скомпилирован в JavaScript, который затем выполняется в браузере, не требуя Node.js на стороне пользователя.
Модульность и библиотеки в TypeScript
TypeScript полностью поддерживает стандарты модульности JavaScript (ES2015 modules, CommonJS). Это позволяет разбивать код на независимые, переиспользуемые части (модули), импортировать и экспортировать функции, классы, интерфейсы и другие элементы. Это делает код более организованным и управляемым. Также TypeScript предоставляет мощную систему для работы с внешними библиотеками JavaScript. С помощью файлов деклараций типов (.d.ts) можно получить типобезопасное взаимодействие с библиотеками, которые изначально написаны на JavaScript. Репозиторий DefinitelyTyped содержит огромное количество готовых файлов деклараций для популярных библиотек.
Компилятор TypeScript
Компилятор TypeScript (tsc) — это основная программа командной строки, которая преобразует код, написанный на TypeScript, в код, совместимый с JavaScript. Он выполняет несколько ключевых задач: проверку типов, транспиляцию (преобразование синтаксиса в более старую версию JavaScript), генерацию артефактов сборки (JavaScript-файлов, файлов деклараций, карт кода) и, опционально, может управлять процессом сборки проекта.
Архитектура компиляции
Процесс компиляции можно условно разделить на несколько этапов:
- Лексический анализ (Tokenization): Исходный код
.tsфайла разбивается на логические "единицы" — токены (например, ключевые словаfunction,class, идентификаторы, операторы, скобки). - Синтаксический анализ (Parsing): Из токенов строится Абстрактное Синтаксическое Дерево (AST) — внутреннее представление структуры кода, которое компилятор использует для дальнейшей обработки.
- Проверка типов (Type Checking): Это уникальная и ключевая особенность TypeScript. На этом этапе компилятор анализирует AST, используя информацию об объявленных типах, переменных, функциях, классах и т. д., чтобы проверить, соответствуют ли операции в коде этим типам. Например, проверяется, что строка не складывается с числом неявно, что вызывается существующий метод с правильными аргументами и т. д. Это происходит до выполнения кода.
- Эмиссия (Emitting / Transpilation): Компилятор генерирует выходные файлы (обычно
.js, но также.d.tsи.js.map). В процессе эмиссии TypeScript-специфичный синтаксис (такой как аннотации типов, интерфейсы, дженерики, декораторы) удаляется или трансформируется в стандартный JavaScript, понятный среде выполнения (браузеру или Node.js). Например, аннотация: numberу параметра функции исчезает, аclassможет быть преобразован вfunctionиprototypeв зависимости от целевой версии JavaScript (см. опциюtarget).
Ключевые компоненты и флаги компилятора
tsc(TypeScript Compiler): Команда для запуска компилятора. Ее можно вызывать с различными флагами и аргументами.tsconfig.json: Это файл конфигурации проекта TypeScript. Он указывает компилятору, какие файлы нужно включить в проект (include,exclude), где находятся файлы деклараций (typeRoots,types), какую версию JavaScript нужно использовать в качестве цели (target), какую систему модулей использовать (module), включать ли строгую проверку типов (strict) и множество других опций. Использованиеtsconfig.json— стандартный способ управления настройками компиляции в проекте.target: Определяет версию ECMAScript, в которую будет транспилирован исходный TypeScript-код. Возможные значения:es3,es5,es2015,es2016,es2017,es2018,es2019,es2020,es2021,esnext. Выбор цели зависит от среды выполнения. Например, для поддержки старых браузеров может использоватьсяes5, а для современных Node.js приложений —es2018илиes2020.module: Определяет, как будут транспилированы модульные системы TypeScript (import,export). Возможные значения:commonjs(для Node.js),amd(для RequireJS),es2015/es2020/esnext(для нативных ES-модулей),umd(универсальный формат),system(для SystemJS). Выбор зависит от системы сборки или загрузчика модулей, используемой в проекте.lib: Позволяет указать, какие встроенные API (например, DOM, ES6 Collections,Promise) будут доступны в целевой среде выполнения. Это нужно для компилятора, чтобы знать, какие типы можно использовать без необходимости их полифила. Например, для браузерного приложения может быть указано["es2015", "dom"], а для Node.js —["es2015"].outDir: Указывает каталог, в который будут помещены скомпилированные JavaScript-файлы.rootDir: Указывает корневой каталог исходных файлов TypeScript. Компилятор использует это для определения структуры каталогов в выходной директории.declaration(d): Если включено, компилятор генерирует файлы деклараций (.d.ts) для каждого скомпилированного файла TypeScript. Эти файлы описывают публичный API модуля (типы, интерфейсы, классы, функции) и необходимы для использования библиотеки, написанной на TypeScript, в других TypeScript-проектах.sourceMap(sourcemap): Если включено, компилятор генерирует файлы карты кода (.js.map). Эти файлы позволяют отображать код, отлаживаемый в браузере или другом инструменте, обратно на исходный TypeScript-код, что значительно упрощает отладку.strict: Включает группу флагов, обеспечивающих максимально строгую проверку типов. ВключаетnoImplicitAny,strictNullChecks,strictFunctionTypes,strictBindCallApply,strictPropertyInitialization,noImplicitThis,useUnknownInCatchVariables. Использованиеstrictрекомендуется для повышения надежности кода.noImplicitAny: Выдает ошибку, если компилятору не удается вывести тип и он вынужден использоватьany.strictNullChecks: Включает строгую проверку наnullиundefined. Переменная типаstring, например, не может быть присвоенаnullилиundefinedбез явного указания этого в типе (например,string | null).allowJs: Позволяет компилятору включать в проект файлы JavaScript (.js) наряду с TypeScript (.ts).checkJs: При использованииallowJsпозволяет выполнять проверку типов и в файлах JavaScript.moduleResolution: Определяет алгоритм, по которому компилятор разрешает пути импорта (import). Наиболее распространенный вариант —"node", который использует ту же логику, что и Node.js.
tsserver (TypeScript Server)
tsserver — это изолированный (отдельный) процесс, который запускается в фоне. Его основная цель — обеспечить быструю и эффективную работу редакторов кода (например, VS Code) и IDE при работе с TypeScript. Он предоставляет такие функции, как:
- Автодополнение (IntelliSense): Предлагает доступные методы, свойства, типы на основе текущего контекста.
- Навигация по коду: Переход к определению переменной, функции, типа.
- Поиск ссылок: Показывает, где используется тот или иной элемент кода.
- Переименование: Безопасное переименование переменных, функций, классов с обновлением всех ссылок.
- Рефакторинг: Предлагает и выполняет шаблонные операции изменения кода.
- Диагностика в реальном времени: Показывает ошибки и предупреждения по мере ввода кода, не дожидаясь запуска
tsc.
Tsserver загружает и кэширует информацию о проекте (AST, типы), что позволяет ему быстро отвечать на запросы редактора, даже в больших проектах. Он отслеживает изменения файлов и обновляет свою модель проекта соответствующим образом.
Языковые службы (Language Services)
Под "языковыми службами" понимаются функции, предоставляемые TypeScript-компилятором (в частности, через tsserver) для интеграции с редакторами и IDE. Это включает в себя:
- Проверку типов и предоставление информации об ошибках.
- Инспекцию кода (например, поиск определений, сигнатур функций).
- Навигацию по коду (Go to Definition, Find All References).
- Рефакторинг и автозаполнение.
Эти службы реализуются компилятором и доступны через его API, что позволяет разработчикам плагинов и поддержке языка в редакторах использовать мощные возможности TypeScript.
Транспиляция и проверка кода на наличие ошибок
Транспиляция — это процесс преобразования исходного кода из одного языка в другой, часто с сохранением семантики, но с целью совместимости с другой средой. В случае TypeScript, это означает:
- Преобразование современного синтаксиса (например,
async/await, деструктуризация,class) в синтаксис, поддерживаемый целевой версией JavaScript (заданной вtarget). - Удаление аннотаций типов, интерфейсов, псевдонимов типов и другого синтаксиса, специфичного для TypeScript.
- Генерация JavaScript-кода, который может быть выполнен в браузере или Node.js.
Проверка кода на ошибки происходит во время компиляции. Это ключевое отличие от JavaScript, где многие ошибки выявляются во время выполнения. TypeScript анализирует типы и структуру кода до выполнения, что позволяет:
- Выявлять ошибки типов (например, вызов метода, которого нет у типа).
- Проверять корректность использования
nullиundefined(при включенииstrictNullChecks). - Обнаруживать несуществующие свойства объектов.
- Обеспечивать более безопасное рефакторинг (например, переименование метода не сломает вызовы в других местах, если типы указаны корректно).
Если компилятор находит ошибки типов, он выдаст предупреждения (если noEmitOnError не установлен в true), но по умолчанию все равно сгенерирует JavaScript-файл. Однако наличие ошибок означает, что код потенциально небезопасен.
Типы данных в TS такие же как в JS?
Не совсем. TypeScript включает в себя все типы данных, существующие в JavaScript, но добавляет к ним свои собственные, специфичные для системы типов. Это позволяет моделировать более сложные и безопасные структуры данных.
- Примитивы (те же, что в JS):
number(все числа, включаяNaN,Infinity)string(строки)boolean(true/false)symbol(уникальные идентификаторы)bigint(целые числа произвольной длины)undefinednull(хотя в строгом режимеstrictNullChecksон рассматривается как отдельный тип)
- Объекты (те же, что в JS):
object(все, что не является примитивом, включая массивы, функции, обычные объекты)Function(специальный подтипobject)
- Типы, специфичные для TypeScript:
any(отключает проверку типов, использовать с осторожностью)unknown(безопасная альтернативаany, требует проверки перед использованием)never(тип для функций, которые никогда не возвращают значение (например, выбрасывают исключение))void(тип для функций, которые ничего не возвращают явно (return;илиreturn undefined;))- Объединения (Union Types):
string | number(значение может быть строкой ИЛИ числом) - Пересечения (Intersection Types):
User & Admin(объект должен иметь свойства иUser, иAdmin) - Интерфейсы и псевдонимы типов (
type): Позволяют определять сложные структуры типов. - Типы литералов:
'exact_string',42,true(указывает конкретное значение) - Массивы:
number[]илиArray<number> - Кортежи:
[string, number](массив фиксированной длины с известными типами на каждой позиции) - Enum (Перечисления):
enum Color { Red, Green, Blue }(ограниченный набор именованных значений) - Функции:
(a: number, b: number) => number(сигнатура функции) - Дженерики (Generics):
Array<T>,Promise<T>(позволяют создавать типы, параметризованные другими типами)
Таким образом, TypeScript расширяет систему типов JavaScript, делая её статической и более выразительной.
Основные типы - number, string, boolean, object, array, tuples, enum
- number: Представляет все числовые значения в JavaScript (целые, дробные,
NaN,Infinity).let a: number = 1;
let b: number = -10;
let c: number = 3.5;
let d: number = 10_000; // Числовой разделитель для читаемости - string: Представляет строковые значения.
let a: string = 'A';
let b: string = "B";
let c: string = `Template ${a}`; // Шаблонная строка - boolean: Представляет логические значения
trueилиfalse.let a: boolean = true;
let b: boolean = false; - object: Тип, представляющий любой не-примитивный объект. Обычно используется не сам по себе, а для указания, что переменная не является примитивом.
let a: object = { x: 1 }; // Объект
let b: object = [1, 2, 3]; // Массив - тоже объект
let c: object = new Date(); // Объект Date - тоже объект
// Но let d: object = 42; // Ошибка, number - примитив - array: Тип для массивов. Можно указать тип элементов.
let numbers: number[] = [1, 2, 3]; // Массив чисел
let names: Array<string> = ["Alice", "Bob"]; // Массив строк (альтернативный синтаксис)
let mixed: (string | number)[] = ["one", 2]; // Массив с разными типами (Union Type) - tuples (Кортежи): Массивы фиксированной длины, где тип каждого элемента на конкретной позиции известен.
let person: [string, number] = ["Alice", 30]; // [Имя: строка, Возраст: число]
// person = [30, "Alice"]; // Ошибка: неправильный порядок типов
// person = ["Bob"]; // Ошибка: недостаточно элементов
// person = ["Charlie", 25, true]; // Ошибка: слишком много элементов - enum (Перечисления): Позволяет задать именованный набор констант.
enum Direction {
Up, // 0 по умолчанию
Down, // 1
Left, // 2
Right // 3
}
let currentDirection: Direction = Direction.Up; // Тип Direction, значение Direction.Up
enum Status {
Success = "SUCCESS", // Можно задать строковые значения
Error = "ERROR"
}
let result: Status = Status.Success;
Обязательно ли указывать тип let name: type (и только ли при объявлении? а при присвоении или использовании?)
Типы в TypeScript можно указывать явно, но это не всегда обязательно.
- При объявлении:
let name: string;- Явно указывается, чтоnameбудет строкой. Пока переменной не присвоено значение, её внутреннее состояние может бытьundefined, но тип объявлен какstring. Позже можно присвоить только строку. - При объявлении с инициализацией:
let name = "Alice";- Компилятор выводит типnameкакstringна основе значения"Alice". Явное указание типа не требуется. - При присвоении:
name = 42;- Еслиnameобъявлена какstring(явно или через вывод), то присвоение числа42вызовет ошибку типов. - При использовании:
console.log(name.length);- Компилятор знает, чтоnameэтоstring(из объявления или вывода), и предоставляет доступ к методамstring, таким какlength.
Типы указываются при объявлении переменной, параметра функции, возвращаемого значения функции и т. д. Компилятор затем выводит типы для выражений и переменных на основе контекста и ранее объявленных/выведенных типов. Явное указание типа нужно, когда компилятор не может его вывести или когда вы хотите сузить тип (например, указать литеральный тип 'specific' вместо string).
Типы для объектов (function test(object: { property: type })) и вложенных объектов
Тип объекта можно указать inline (встроенно) в сигнатуре функции или определить отдельно через interface или type.
- Inline (внутри функции):
function printUser(user: { name: string; age: number }) {
console.log(`Name: ${user.name}, Age: ${user.age}`);
}
// Вызов с объектом, соответствующим структуре
printUser({ name: "Alice", age: 30 }); // OK
// printUser({ name: "Bob" }); // Ошибка: нет свойства 'age' - Вложенные объекты:
function processConfig(config: { server: { host: string; port: number }; debug: boolean }) {
console.log(`Connecting to ${config.server.host}:${config.server.port}, Debug: ${config.debug}`);
}
processConfig({
server: { host: "localhost", port: 8080 }, // Вложенный объект
debug: true
}); // OK - Определение через
interfaceилиtype:interface ServerConfig {
host: string;
port: number;
}
interface AppConfig {
server: ServerConfig; // Используем другой интерфейс
debug: boolean;
}
function processConfig(config: AppConfig) {
// ...
}
Вся проверка типов выполняется на уровне TS
Да, проверка типов в TypeScript происходит во время компиляции, то есть до выполнения JavaScript-кода. Компилятор анализирует TypeScript-код, используя информацию о типах, и выдает ошибки, если обнаруживает несоответствия (например, сложение строки с объектом). Эти ошибки не позволяют сгенерировать JavaScript-код (если не отключена опция noEmitOnError), или генерируется код, но с предупреждениями. Это позволяет выявлять ошибки на стадии написания кода, а не во время выполнения.
Указание типов в функциях (parameter: type) и возвращаемого значения (function(): type)
Типы параметров и возвращаемого значения функции можно указывать как в обычных функциях, так и в стрелочных функциях.
- Параметры:
function myFunc(param1: string, param2: number) { ... }- Типы параметров указываются после их имени через:. - Возвращаемое значение:
function myFunc(): boolean { ... }- Тип возвращаемого значения указывается после списка параметров через:.Если возвращаемый тип не указан, компилятор старается его вывести на основеfunction add(a: number, b: number): number {
return a + b; // Компилятор проверит, что возвращается число
}
const multiply = (x: number, y: number): number => {
return x * y;
};
// Стрелочная функция с телом {}
const greet = (name: string): string => {
return `Hello, ${name}`;
};
// Стрелочная функция с выражением (неявный return)
const square = (n: number): number => n * n;returnвыражений внутри функции.
Продвинутые типы
TypeScript предоставляет мощные инструменты для создания сложных и выразительных типов.
-
Высшие типы (any, unknown и неявно заданные типы, а также почему
anyне надо юзать):any: Отключает проверку типов для значения. Позволяет делать с ним всё, что угодно. Это нарушает безопасность типов и может привести к ошибкам времени выполнения. Использоватьanyследует как можно реже, только когда действительно невозможно описать тип иначе (и даже тогда, стоит попытаться).unknown: Безопасная альтернативаany. Значение типаunknownнельзя использовать напрямую до тех пор, пока не будет выполнена проверка типа (type guard), которая сужает его до более конкретного типа.- Неявно заданные типы: Это когда компилятор не может вывести тип и вынужден использовать
any(например, для параметров функции без аннотации или для переменных, инициализированныхnullилиundefined, если не включенstrictрежим). ОпцияnoImplicitAnyпомогает выявить такие места и обязывает указать тип явно.
-
Примитивные типы и составные типы:
- Примитивные:
string,number,boolean,symbol,bigint,undefined,null. - Составные (Compound Types): Это типы, создаваемые из других типов, например, объединения (
|), пересечения (&), массивы (T[]), объекты{ prop: Type }, функции(arg: T) => R.
- Примитивные:
-
Супертипы, объектные типы, обобщённые и условные типы:
- Супертипы: Тип
Aявляется супертипом типаB, еслиBможно использовать везде, где ожидаетсяA. Это важно для понимания наследования и совместимости. В структурной типизации это определяется формой (набором свойств/методов), а не именем. - Объектные типы:
{ name: string; age: number }- тип, описывающий структуру объекта. - Обобщённые типы (Generics): Позволяют создавать типы и функции, параметризованные другими типами, делая их более переиспользуемыми и типобезопасными. Пример:
Array<T>,Promise<T>,function identity<T>(arg: T): T. - Условные типы: Позволяют выбирать тип в зависимости от условия. Форма:
SomeType extends OtherType ? TrueType : FalseType. Используются для создания сложных маппингов типов. Пример:NonNullable<T>,ReturnType<T>,Extract<T, U>.
- Супертипы: Тип
-
Связи типов (Подтипы, Супертипы, Вариантность):
- Подтипы/Супертипы: Как описано выше. В TypeScript используется структурная типизация, поэтому
{ a: number }является подтипом{ a: number; b?: string }. - Вариантность: Описывает, как отношение подтипов распространяется на сложные типы, содержащие другие типы, например,
Array<A>иArray<B>, или(x: A) => Rи(x: B) => R.- Ковариантность:
A <: BозначаетContainer<A> <: Container<B>(например, для возвращаемых значений функций). - Контрвариантность:
A <: BозначаетContainer<B> <: Container<A>(например, для типов параметров функций). - Инвариантность:
Container<A>иContainer<B>не связаны, даже еслиA <: B(например, для типов параметров методов в классах, или дляArray<T>в строгом смысле). - Бивариантность: Редко используется, означает, что работает и ковариантность, и контрвариантность (потенциально небезопасно).
- Ковариантность:
- Подтипы/Супертипы: Как описано выше. В TypeScript используется структурная типизация, поэтому
-
Вариантность, ковариантность, инвариантность, контрвариантность, бивариантность в TS:
- Объекты/Классы: Ковариантны в типах свойств (только для чтения/поля, не для методов, если они изменяют состояние).
- Массивы: Инвариантны.
number[]не является подтипомstring[]и наоборот, даже еслиnumberиstringсвязаны. - Функции: Контрвариантны в типах параметров и ковариантны в типе возвращаемого значения.
(x: Animal) => stringявляется подтипом(x: Dog) => string, еслиDog <: Animal. - Дженерики: По умолчанию инвариантны. Поведение можно настроить с помощью условных типов и
infer.
-
Уточнение типов (Type Narrowing): Как TypeScript "сужает" тип на основе проверок (typeof (к примеру
if(typeof name == 'string')как проверка внешних данных на валидные типы), instanceof, in, строгого равенства): Это процесс, при котором компилятор TypeScript, на основе логических проверок в коде, определяет, что тип переменной в определенной ветке кода является более узким, чем он был ранее.typeof: Проверяет тип примитива.function processValue(value: string | number) {
if (typeof value === 'string') {
// В этой ветке TypeScript знает, что value - это string
console.log(value.toUpperCase()); // OK
} else {
// Здесь value - это number
console.log(value.toFixed(2)); // OK
}
}instanceof: Проверяет, является ли объект экземпляром класса.if (error instanceof TypeError) {
// error теперь типа TypeError
}in: Проверяет, существует ли свойство в объекте.interface Bird { type: 'bird'; fly(): void; }
interface Dog { type: 'dog'; bark(): void; }
function act(animal: Bird | Dog) {
if ('fly' in animal) {
// animal - это Bird
animal.fly();
} else {
// animal - это Dog
animal.bark();
}
}- Строгое равенство (
===,!==): Помогает сузить литеральные типы.type Status = "success" | "error";
function handleStatus(s: Status) {
if (s === "success") {
// s теперь "success"
}
}
-
Декларация типа, файл с расширением
.d.ts, внешние декларации переменных, типов, модулей:- Файл
.d.ts: Файл, содержащий только объявления типов (без реализации). Используется для описания типов стороннего JavaScript-кода или API. - Декларация типа (
declare): Ключевое словоdeclareиспользуется в.d.tsфайлах или в режиме скриптов для указания, что переменная, функция, класс и т.д. существуют в JavaScript-коде, но не определены в текущем TypeScript-файле.// types.d.ts
declare let process: {
env: { [key: string]: string | undefined };
}; - Внешние декларации:
declare var,declare function,declare class,declare namespace,declare module.
- Файл
-
TSC-УСТАНОВКИ: ТИПЫ И
TYPEROOTS(ИСТОЧНИКИ ТИПОВ):types: Вtsconfig.jsonпозволяет явно указать, какие пакеты с типами (изnode_modules/@typesилиtypeRoots) должны быть включены. Если не указано, компилятор ищет все пакеты в@types.typeRoots: Вtsconfig.jsonпозволяет указать каталоги, где компилятор будет искать пакеты с типами (вместо стандартногоnode_modules/@types).
-
Объявление типов:
string,number,boolean,any,unknown,void: Это основные встроенные типы TypeScript, как описано выше. Их можно использовать для аннотации переменных, параметров, возвращаемых значений. -
Аннотирование типов и псевдонимы типов (
type):- Аннотирование:
let variable: TypeName;- прямое указание типа. - Псевдоним типа (
type): Позволяет создать имя для существующего типа.type Age = number;
type Person = { name: string; age: Age };
let user: Person = { name: "Alice", age: 30 };
- Аннотирование:
-
Совместимость, согласование, имитация номинальных типов, утверждения типов, ненулевые утверждения и прочие утверждения:
- Совместимость: Правила, по которым TypeScript определяет, можно ли использовать один тип в месте другого. Основано на структурной типизации.
- Согласование (Assignability): Можно ли присвоить значение одного типа другому.
- Имитация номинальных типов: Использование уникальных символов (
unique symbol) или пересечений с объектами-маркерами для создания типов, которые не совместимы, несмотря на одинаковую структуру (см. раздел "Имитация номинальных типов" в книге). - Утверждения типов (
as,<>): Явное указание компилятору, что значение имеет определенный тип. Используется, когда разработчик знает больше, чем компилятор. Опасно, если используется неправильно.let someValue: any = "this is a string";
let strLength: number = (someValue as string).length; - Ненулевое утверждение (
!): Указывает, что значение не равноnullилиundefined, несмотря на его тип.let possiblyNull: string | null = getString();
console.log(possiblyNull!.toUpperCase()); // OK, если вы уверены, что не null - Утверждения явного присваивания (
!): Используется при объявлении переменной, чтобы сообщить компилятору, что она будет инициализирована позже (например, внутриifили в другой функции), избегая ошибкиnot initialized.let userName!: string; // Компилятор не будет жаловаться на использование до инициализации
initializeUser(); // Где-то здесь userName получает значение
console.log(userName); // OK
-
Работа с переменными, функциями, классами: Это общее понятие, охватывающее использование типов при объявлении и использовании этих элементов. Примеры выше уже демонстрируют это.
-
Классы: Наследование (
extends), модификаторы доступа (public,private,protected),readonly, абстрактные классы (abstract), статические методы:- extends: Позволяет одному классу наследовать свойства и методы другого.
- public, private, protected:
public: Доступно везде (по умолчанию).private: Доступно только внутри класса, в котором объявлено.protected: Доступно внутри класса и его подклассов.
- readonly: Позволяет инициализировать свойство только при объявлении или в конструкторе. После этого его нельзя изменить.
- abstract: Ключевое слово для создания абстрактных классов и методов. Абстрактный класс нельзя инстанцировать напрямую, только наследовать. Абстрактные методы должны быть реализованы в подклассах.
- Статические методы: Методы, принадлежащие классу, а не экземпляру (
static methodName() { ... }).
-
Сигнатуры индексов, опциональные свойства (
?),readonly.- Сигнатуры индексов:
{ [key: string]: number }- позволяет объекту иметь произвольное количество свойств с ключами указанного типа и значениями указанного типа. - Опциональные свойства (
?):prop?: type- свойство может отсутствовать в объекте. readonly:readonly prop: type- свойство можно только читать, не изменять после инициализации.
- Сигнатуры индексов:
-
Литеральные типы: Тип, который представляет ровно одно значение: строку, число или булево. Полезны для ограничения допустимых значений.
type Direction = "up" | "down" | "left" | "right";
type StatusCode = 200 | 400 | 404 | 500; -
typeof, void:
- typeof: Оператор JavaScript, возвращающий строку, описывающую тип значения. В TypeScript используется для уточнения типов и в аннотациях типов (например,
let b: typeof a;). - void: Тип, обычно используемый как возвращаемый тип функций, которые ничего не возвращают явно (
return;илиreturn undefined;).
- typeof: Оператор JavaScript, возвращающий строку, описывающую тип значения. В TypeScript используется для уточнения типов и в аннотациях типов (например,
-
Перечисления (
enum): Как описано выше. Способ дать имена наборам числовых или строковых значений. -
Использование в браузере и Node.js: TypeScript компилируется в JavaScript, который затем выполняется в браузере или Node.js. Настройка
targetиlibвtsconfig.jsonдолжна соответствовать возможностям среды выполнения. Для браузеров часто используетсяtarget: es5илиes2015иlib: ["es2015", "dom"]. Для Node.jstargetзависит от версии Node.js,libобычно["es2015"]или выше.TypeScript не требует Node.js для своей работы как язык (его можно компилировать и выполнять в других средах, например, в браузере), но Node.js часто используется в качестве среды выполнения для компилятора TypeScript (
tsc) и для запуска скомпилированного JavaScript-кода на сервере. Компилятор TypeScript (tsc) сам по себе является приложением Node.js. Поэтому, чтобы установить и использоватьtsc, обычно требуется установленный Node.js. Однако, TypeScript-код может быть скомпилирован в JavaScript, который затем выполняется в браузере, не требуя Node.js на стороне пользователя.TypeScript полностью поддерживает стандарты модульности JavaScript (ES2015 modules, CommonJS). Это позволяет разбивать код на независимые, переиспользуемые части (модули), импортировать и экспортировать функции, классы, интерфейсы и другие элементы. Это делает код более организованным и управляемым. Также TypeScript предоставляет мощную систему для работы с внешними библиотеками JavaScript. С помощью файлов деклараций типов (
.d.ts) можно получить типобезопасное взаимодействие с библиотеками, которые изначально написаны на JavaScript. РепозиторийDefinitelyTypedсодержит огромное количество готовых файлов деклараций для популярных библиотек. -
Контекстная типизация: Как TypeScript может выводить типы параметров на основе контекста (например, в callback'ах). Компилятор может выводить типы на основе контекста, в котором выражение используется. Особенно полезно для функций обратного вызова.
function callWithNumber(callback: (n: number) => void) {
callback(42); // Передаём число
}
callWithNumber(function(n) { // TypeScript выводит, что 'n' - это number
console.log(n.toFixed()); // OK
});
callWithNumber(n => console.log(n.toFixed())); // Вывод работает и для стрелочных функций -
Интерфейсы и типы (
interface,type):interface: Основной способ определения формы объектов. Поддерживает наследование (extends), слияние деклараций (declaration merging).type: Более общий способ создания псевдонимов для типов, включая объединения, примитивы, объекты, кортежи и т. д. Не поддерживает наследование так же напрямую, какinterface.
-
this в функциях/методах: Типизация this. В функциях, особенно в методах классов,
thisможет быть непредсказуемым. TypeScript позволяет указать ожидаемый типthisкак первый неявный параметр функции.class MyClass {
name: string;
constructor(name: string) { this.name = name; }
// Указываем, что 'this' внутри метода должен быть типа MyClass
greet(this: MyClass) {
console.log(`Hello, ${this.name}`);
}
} -
Классы:
class,constructor,extends,implements:class: Основной синтаксис для определения классов.constructor: Специальный метод для инициализации экземпляра.extends: Для наследования от другого класса.implements: Для указания, что класс реализует один или несколько интерфейсов (должен содержать все члены, определённые в интерфейсе).
-
Расширение типов и прочие манипуляции с типами, служебные типы:
- Расширение: В основном через
interface(слияние) илиtype(пересечения&). - Манипуляции: Использование условных типов, отображённых типов, ключевых слов
keyof,typeof,infer. - Служебные типы (Utility Types): Предопределённые обобщённые типы для распространённых операций:
Partial<T>,Required<T>,Pick<T, K>,Omit<T, K>,Record<K, T>,ReturnType<F>,Parameters<F>и другие.
- Расширение: В основном через
Дженерики (Generics): <T>
Дженерики позволяют создавать компоненты (функции, классы, интерфейсы, типы) которые работают с разными типами, сохраняя при этом информацию о типе и обеспечивая типобезопасность.
- Синтаксис:
<T>или<T, U, V>.T,U,V- параметры типа. Их можно назвать как угодно, ноT(Type),U,K(Key),V(Value) - общепринятые имена. - Пример функции:
function identity<T>(arg: T): T {
return arg; // Возвращаем значение того же типа, что и получили
}
let output1 = identity<string>("myString"); // Явно указываем T = string
let output2 = identity(42); // Компилятор выводит T = number - Пример класса:
class Container<T> {
private value: T;
constructor(value: T) {
this.value = value;
}
getValue(): T {
return this.value;
}
setValue(value: T): void {
this.value = value;
}
}
let stringContainer = new Container<string>("Hello");
let numContainer = new Container(123); // T выведен как number - Ограничения (Constraints): Можно ограничить, какие типы могут быть переданы в дженерик, используя
extends.interface Lengthwise {
length: number;
}
function logProperty<T extends Lengthwise>(obj: T): void {
console.log(obj.length); // OK, потому что T обязан иметь .length
}
logProperty("abc"); // OK, string имеет length
logProperty([1, 2, 3]); // OK, array имеет length
// logProperty(42); // Ошибка: number не имеет length
Декораторы
Декораторы - это экспериментальная возможность TypeScript, позволяющая добавлять аннотации и метапрограммирование к объявлениям классов, методов, свойств, параметров и аксессоров. Они реализуются как специальные функции, вызываемые компилятором. На момент TypeScript 5.6 декораторы официально стандартизованы в ES2025, но реализация в TypeScript может отличаться от старой экспериментальной версии. Использование требует включения флага experimentalDecorators в tsconfig.json (для старой версии) или decoratorMetadata и emitDecoratorMetadata (для новых возможностей, если используется рефлексия). Они не компилируются в JavaScript по умолчанию, как аннотации в Java. Их часто используют в фреймворках (например, Angular).
Типы кортежей
Кортежи (tuple) - это тип, представляющий массив с фиксированной длиной и известными типами элементов на каждой позиции.
- Синтаксис:
[Type1, Type2, ...] - Пример:
let x: [string, number];
x = ["hello", 10]; // OK
// x = [10, "hello"]; // Ошибка
// x = ["hello"]; // Ошибка
// x = ["hello", 10, true]; // Ошибка
// Деструктуризация
const [str, num] = x;
// str: string, num: number
- Опциональные элементы (начиная с TS 3.0):
[Type1, Type2?, Type3?]- элементы после первого опционального становятся опциональными. - Оставшиеся элементы (rest):
[string, ...number[]]- кортеж с первым элементомstring, а затем любое количествоnumber.
Синтаксис
Синтаксис TypeScript включает весь синтаксис JavaScript, а также дополнительные элементы для аннотации типов и определения типов:
- Аннотации типов:
: TypeName(переменные, параметры, возвращаемые значения). - Объявление типов:
type TypeName = ...,interface InterfaceName { ... }. - Дженерики:
<T>. - Модификаторы доступа:
public,private,protected. - Ключевые слова:
abstract,readonly,enum,namespace,declare,keyof,typeof,infer,in. - Специальные типы:
any,unknown,never,void.
Как мигрировать с JS на TS - добавить TSC в проект, начать проверку типов, перенести в TS файл за файлом (переименование файлов в .ts), установить декларации типов для зависимостей, включить режим strict (активация строгости)
Это подробно описано в разделе "Поэтапная миграция из JavaScript в TypeScript" в книге Бориса Черного (Глава 11).
- Добавить TSC: Установить
typescriptкак devDependency (npm install typescript --save-dev) и создатьtsconfig.json. - Включить
allowJs: Позволить компилятору читать.jsфайлы. - (Опционально) Включить
checkJs: Начать проверять типы в.jsфайлах. - (Опционально) Добавлять JSDoc: Для улучшения вывода типов в
.jsфайлах. - Переименовывать файлы:
.js->.ts(или.tsxдля JSX). Исправлять ошибки типов по мере перехода. - Установить
@types: Для зависимостей, у которых нет встроенных деклараций типов (npm install @types/package-name --save-dev). - Включить
strict: По возможности, постепенно включать флаги строгости (noImplicitAny,strictNullChecksи т. д.).
Definitely Typed
Это центральный репозиторий (@DefinitelyTyped) для файлов деклараций типов (*.d.ts) сторонних JavaScript-библиотек. Эти пакеты публикуются на npm под префиксом @types/. Например, @types/react, @types/node. Это позволяет использовать сторонние библиотеки в TypeScript-проектах с типобезопасностью.
Функции-генераторы и Итераторы
- Функции-генераторы: Функции, определённые с
function*, которые могут "приостанавливать" своё выполнение с помощьюyieldи возвращатьIterator(илиAsyncIteratorдляasync function*). Позволяют создавать ленивые последовательности.function* fibonacci(): Iterator<number> {
let a = 0, b = 1;
while (true) {
yield a;
[a, b] = [b, a + b];
}
}
const fib = fibonacci();
console.log(fib.next().value); // 0
console.log(fib.next().value); // 1
console.log(fib.next().value); // 1
console.log(fib.next().value); // 2 - Итераторы: Объекты, реализующие протокол итерации (метод
next()или символSymbol.iterator). Позволяют перебирать коллекции (массивы,Map,Set, строки и т. д.) с помощьюfor...of.
Примеси (Mixins): Как добавлять функциональность классам композиционно, а не через наследование.
Примеси - это шаблон программирования, позволяющий "смешивать" (composing) функциональность в классы без использования множественного наследования (которое TypeScript не поддерживает). Реализуется через функции, которые принимают конструктор класса и возвращают новый класс, расширяющий переданный и добавляющий новую функциональность. Это часто делается с использованием дженериков и declare.
TypeORM
Это библиотека для TypeScript (и JavaScript), реализующая шаблон Object-Relational Mapping (ORM). Она позволяет работать с реляционными базами данных (PostgreSQL, MySQL, MariaDB, SQLite, MS SQL Server, Oracle) и MongoDB, используя классы TypeScript и декораторы для описания сущностей (таблиц). TypeORM предоставляет типобезопасный интерфейс для выполнения запросов, миграций и управления сущностями.