TypeScript для начинающих: Основы типизированной разработки

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

1. Введение в TypeScript: установка окружения, настройка компилятора и первый запуск

Введение в TypeScript: установка окружения, настройка компилятора и первый запуск

Добро пожаловать в курс «TypeScript для начинающих». Если вы читаете эту статью, значит, вы решили сделать важный шаг в своём развитии как разработчика. JavaScript — прекрасный язык, на котором держится весь современный веб, но по мере роста проектов его гибкость часто превращается в источник трудноуловимых ошибок. Здесь на сцену выходит TypeScript.

В этой первой статье мы не будем сразу погружаться в сложные типы или дженерики. Наша цель — построить надежный фундамент. Мы разберем, что такое TypeScript, подготовим ваш компьютер к работе, установим необходимые инструменты и напишем нашу первую программу. К концу этого урока у вас будет полностью настроенная среда разработки, готовая к покорению типизированного мира.

Что такое TypeScript и зачем он нам нужен?

TypeScript (часто сокращаемый до TS) — это язык программирования, разработанный компанией Microsoft. Но называть его просто «другим языком» было бы не совсем верно. Технически, TypeScript является надмножеством (superset) JavaScript.

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

!Схематичное изображение того, как TypeScript включает в себя JavaScript и расширяет его возможности.

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

Ключевые преимущества:

* Раннее выявление ошибок: Компилятор укажет на ошибку еще до запуска программы. * Улучшенный автокомплит: Редакторы кода (IDE) лучше понимают структуру вашего кода и предлагают более точные подсказки. * Читаемость: Типы служат отличной документацией. Глядя на функцию, вы сразу понимаете, что она принимает и что возвращает. * Безопасный рефакторинг: Изменение структуры данных в одном месте автоматически подсветит все места, где эти данные используются неправильно.

Подготовка окружения: Node.js и npm

Браузеры не умеют исполнять TypeScript напрямую. Они понимают только JavaScript. Поэтому нам нужен инструмент, который превратит наш красивый типизированный код в обычный JS, понятный браузеру или серверу. Этот процесс называется транспиляцией (transpilation).

Компилятор TypeScript написан на JavaScript, поэтому для его работы нам потребуется среда выполнения Node.js.

Шаг 1: Установка Node.js

Если у вас уже установлен Node.js, вы можете пропустить этот шаг. Если нет:

  • Перейдите на официальный сайт Node.js.
  • Скачайте LTS-версию (Long Term Support) для вашей операционной системы. Она наиболее стабильна.
  • Установите её, следуя инструкциям установщика.
  • Чтобы проверить, что установка прошла успешно, откройте терминал (командную строку) и введите:

    Вы должны увидеть версию Node.js (например, v18.16.0 или новее).

    Вместе с Node.js автоматически устанавливается пакетный менеджер npm (Node Package Manager). Проверьте его версию:

    Установка компилятора TypeScript

    Теперь, когда у нас есть фундамент, мы можем установить сам TypeScript. Мы сделаем это глобально, чтобы команда tsc (TypeScript Compiler) была доступна в любой папке вашего компьютера.

    Откройте терминал и выполните следующую команду:

    Флаг -g означает «global» (глобально). В некоторых системах (macOS или Linux) вам может потребоваться добавить sudo перед командой, если у вас нет прав администратора.

    После завершения установки проверим версию компилятора:

    Если вы видите номер версии (например, Version 5.3.0), поздравляю — ваш компьютер готов к работе с TypeScript!

    Ваш первый TypeScript-файл

    Давайте напишем простейшую программу, чтобы увидеть магию в действии. Создайте новую папку для нашего проекта, например hello-ts, и откройте её в вашем любимом редакторе кода (рекомендуется VS Code, так как он имеет встроенную поддержку TS).

    Создайте файл с именем index.ts. Обратите внимание на расширение .ts.

    Вставьте в файл следующий код:

    Давайте разберем, что здесь происходит:

  • const message: string — мы явно указали, что переменная message может содержать только строку. Если вы попытаетесь присвоить ей число, редактор подчеркнет это красным.
  • name: string — аргумент функции тоже строго типизирован.
  • : void — это тип возвращаемого значения функции. void означает, что функция ничего не возвращает (она просто выводит текст в консоль).
  • Попытка запуска

    Если вы попытаетесь запустить этот файл с помощью Node.js напрямую (node index.ts), вы, скорее всего, получите ошибку (если не используете специальные загрузчики). Node.js не понимает аннотации типов вроде : string. Нам нужно скомпилировать код.

    Компиляция и запуск

    Вернитесь в терминал, находясь в папке проекта, и выполните команду:

    Если вы не увидели никаких сообщений об ошибках, значит, компиляция прошла успешно. Посмотрите в папку с файлами. Вы увидите, что рядом с index.ts появился новый файл — index.js.

    !Визуализация процесса превращения TypeScript кода в JavaScript код через компилятор.

    Откройте index.js. Он будет выглядеть примерно так:

    Заметьте: все типы исчезли! Компилятор «вырезал» их, оставив чистый JavaScript, который теперь можно запустить:

    В консоли вы увидите: Привет, мир TypeScript! Меня зовут Студент.

    Настройка компилятора: tsconfig.json

    Запускать tsc filename.ts каждый раз неудобно, особенно если файлов много. К тому же, мы хотим контролировать, как именно компилируется наш код (например, в какую версию JS превращать синтаксис).

    Для этого используется файл конфигурации tsconfig.json. Чтобы создать его автоматически, выполните в корне вашего проекта команду:

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

    Основные настройки

    Откройте tsconfig.json. Вы увидите JSON-объект с полем compilerOptions. Вот ключевые параметры:

  • target: Определяет версию стандарта JavaScript, в которую будет скомпилирован ваш код. Например, если вы поставите "target": "es5", то все современные фишки (как стрелочные функции или const) будут переписаны в старый синтаксис для совместимости с древними браузерами. Для современной разработки часто ставят "es2016" или новее.
  • module: Указывает систему модулей (например, commonjs для Node.js или esnext для современных фронтенд-сборщиков).
  • rootDir: Папка, где лежат ваши исходные .ts файлы. Хорошей практикой считается держать исходный код в папке src.
  • outDir: Папка, куда компилятор будет складывать готовые .js файлы. Обычно это папка dist или build. Это позволяет не смешивать исходники и результат.
  • strict: Это «главный рубильник» строгости. Если он установлен в true, TypeScript включает максимальный уровень проверок. Настоятельно рекомендую всегда держать эту опцию включенной. Это заставляет вас писать более качественный код и учит правильно работать с типами.
  • Пример простой конфигурации

    Давайте настроим наш проект правильно. Создайте папку src и переместите туда index.ts. Отредактируйте tsconfig.json, оставив (или раскомментировав) следующие строки:

    Теперь, чтобы скомпилировать проект, вам не нужно указывать имя файла. Просто введите в терминале:

    Компилятор автоматически найдет tsconfig.json, прочитает настройки, возьмет все файлы из src и положит результат в dist.

    Автоматическая перекомпиляция (Watch Mode)

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

    Запустите команду:

    Или tsc --watch. Терминал «зависнет» в режиме ожидания. Теперь, как только вы сохраните любой .ts файл в проекте, компилятор мгновенно пересооберет его. Попробуйте изменить текст в index.ts и сохранить файл — вы увидите сообщение о компиляции в терминале.

    Для выхода из этого режима нажмите Ctrl + C.

    Полезный инструмент: ts-node

    В процессе разработки иногда хочется просто запустить TS-файл, не думая о папках dist, компиляции и прочем. Для этого существует утилита ts-node. Она компилирует и исполняет код «на лету» в памяти.

    Установим её (можно локально для проекта):

    Теперь вы можете запускать код одной командой (используя npx для запуска локальных пакетов):

    Это очень удобно для тестов, скриптов и обучения, хотя для продакшена (боевого сервера) всегда следует использовать предварительную компиляцию через tsc.

    Заключение

    Поздравляю! Вы успешно преодолели самый скучный, но необходимый этап — настройку окружения. Теперь у вас есть:

  • Понимание того, что TypeScript — это надстройка над JavaScript.
  • Установленный Node.js и компилятор TypeScript.
  • Настроенный файл tsconfig.json, который раскладывает файлы по полочкам.
  • Первая работающая типизированная программа.
  • В следующей статье мы перейдем к самому интересному — системе типов. Мы узнаем, чем any отличается от unknown, как типизировать объекты и массивы, и почему void — это не совсем «ничего».

    Готовы? Тогда проверьте, что ваш терминал работает, и переходите к следующему уроку!

    2. Базовые типы данных: примитивы, массивы, кортежи, Enums и тип Any

    Базовые типы данных: примитивы, массивы, кортежи, Enums и тип Any

    В предыдущей статье мы успешно подготовили почву: установили Node.js, настроили компилятор TypeScript и создали наш первый проект. Теперь, когда техническая часть позади, пришло время погрузиться в саму суть языка — его систему типов.

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

    В этом уроке мы разберем фундамент: примитивные типы, способы работы со списками данных и специальные конструкции, которых нет в обычном JavaScript.

    Примитивные типы данных

    Начнем с простого. Примитивы в TypeScript практически идентичны тем, что вы используете в JavaScript. Главное отличие — мы явно указываем их тип при объявлении переменной.

    Boolean (Логический тип)

    Самый простой тип данных, который может принимать всего два значения: true (истина) или false (ложь).

    Number (Числовой тип)

    Как и в JavaScript, в TypeScript все числа — это числа с плавающей точкой. TypeScript также поддерживает шестнадцатеричные, двоичные и восьмеричные литералы.

    String (Строковый тип)

    Для работы с текстом используется тип string. Вы можете использовать двойные кавычки, одинарные кавычки или обратные кавычки (шаблонные строки).

    Null и Undefined

    В TypeScript у null и undefined есть свои собственные типы с такими же именами. Их поведение сильно зависит от настройки strictNullChecks в вашем файле tsconfig.json (о котором мы говорили в прошлом уроке).

    Если строгий режим включен (а мы договорились, что он включен), вы не сможете присвоить null переменной типа number или string. Это защищает от одной из самых частых ошибок в программировании — попытки прочитать свойство у несуществующего объекта.

    Массивы (Arrays)

    Когда нам нужно хранить коллекцию данных одного типа, мы используем массивы. В TypeScript есть два способа записать тип массива.

    Способ 1: Использование квадратных скобок

    Это наиболее распространенный и краткий синтаксис. Вы пишете тип элементов, а затем добавляете [].

    Способ 2: Использование дженерика Array

    Этот способ использует угловые скобки. О дженериках мы поговорим в будущих уроках подробно, но пока просто запомните этот синтаксис как альтернативу.

    Оба способа работают идентично. Выбор зависит от стиля кода вашей команды, но первый вариант (type[]) встречается чаще.

    Кортежи (Tuples)

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

    Кортеж (Tuple) — это массив фиксированной длины, где тип каждого элемента известен заранее.

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

    !Визуальное различие между однородным массивом и строго структурированным кортежем.

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

    Важно помнить, что порядок имеет значение. Вы не можете поменять местами элементы, если их типы различаются.

    Enums (Перечисления)

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

    Это невероятно удобно для описания состояний, ролей пользователей, цветов или направлений.

    Числовые Enums

    По умолчанию элементы перечисления получают значения начиная с 0.

    Вы можете изменить начальное значение, и нумерация продолжится от него:

    Строковые Enums

    Для лучшей читаемости и отладки часто используют строковые значения. В этом случае вы должны явно задать значение для каждого элемента.

    Использование Enum делает код намного понятнее, чем использование «магических чисел» или просто строк, разбросанных по всему проекту.

    Тип Any

    Иногда мы не знаем тип переменной, когда пишем код. Эти значения могут приходить из динамического контента (например, от пользователя или сторонней библиотеки). В таких случаях мы хотим отключить проверку типов и позволить переменной пройти компиляцию без ошибок.

    Для этого существует тип any.

    Тип any — это способ сказать компилятору: «Я знаю, что делаю, не проверяй меня». С переменной типа any можно делать что угодно:

    Почему Any — это зло?

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

    > Используйте any только в крайних случаях, например, при постепенном переводе старого JS-проекта на TS. В остальных случаях старайтесь типизировать данные или использовать тип unknown (о котором мы поговорим позже).

    Вывод типов (Type Inference)

    В примерах выше мы явно указывали типы: let x: number = 5. Однако TypeScript достаточно умен, чтобы во многих случаях понять тип самостоятельно. Это называется выводом типов.

    Вам не обязательно писать аннотации типов везде. Если вы инициализируете переменную сразу при объявлении, TypeScript автоматически «запомнит» этот тип. Это делает код чище и лаконичнее.

    Заключение

    Сегодня мы разобрали кирпичики, из которых строятся данные в TypeScript:

  • Примитивы (string, number, boolean) работают так же, как в JS, но с защитой от смены типа.
  • Массивы гарантируют, что в списке лежат только допустимые элементы.
  • Кортежи позволяют создавать строгие структуры фиксированной длины.
  • Enums помогают избавиться от магических значений и делают код читаемым.
  • Any — мощный инструмент, которым не стоит злоупотреблять.
  • Теперь вы можете создавать переменные и структуры данных, будучи уверенными в их надежности. Но что делать, если наши данные сложнее, чем просто число или строка? В следующей статье мы перейдем к одной из самых важных тем — Интерфейсам и Типам объектов, где научимся описывать сложные сущности реального мира.

    3. Структурирование данных: разница между Interface и Type, типизация объектов

    Структурирование данных: разница между Interface и Type, типизация объектов

    Добро пожаловать на третий урок курса «TypeScript для начинающих». В предыдущей статье мы разобрали атомы мира TypeScript — примитивные типы данных, такие как string, number, boolean, а также массивы и кортежи. Это фундамент, на котором строится любое приложение. Однако, если вы посмотрите на реальный код, вы редко увидите переменные, живущие сами по себе. В современной разработке мы мыслим сущностями: Пользователь, Товар, Заказ, Настройка.

    Эти сущности представляют собой объекты. И именно здесь TypeScript раскрывает свою истинную мощь. Сегодня мы научимся описывать форму объектов, разберем два главных инструмента для этого — Interface (Интерфейс) и Type Alias (Псевдоним типа), и наконец ответим на вечный вопрос собеседований: «В чем разница между type и interface?».

    Проблема типизации объектов «в лоб»

    Давайте представим, что мы пишем функцию, которая принимает объект пользователя и выводит приветствие. Используя знания из прошлого урока, мы могли бы написать это так:

    Здесь мы использовали инлайн-типизацию (inline typing). Мы описали структуру объекта прямо в аргументах функции. Это работает, но у такого подхода есть существенные недостатки:

  • Дублирование кода: Если у вас есть еще одна функция, например saveUser, вам придется снова писать { name: string; age: number }.
  • Читаемость: Если объект сложный (содержит вложенные объекты, массивы), сигнатура функции превратится в нечитаемую «кашу».
  • Чтобы решить эту проблему, TypeScript предлагает нам два способа дать имя этой структуре: type и interface.

    Type Alias (Псевдоним типа)

    Ключевое слово type позволяет создать собственное имя для любого типа данных. Это не создание нового типа в техническом смысле, это создание ссылки (алиаса) на существующую структуру.

    Теперь User — это полноценный идентификатор типа, который можно экспортировать и переиспользовать по всему проекту.

    Interface (Интерфейс)

    Второй способ описать структуру объекта — использовать ключевое слово interface. Интерфейсы в TypeScript — это способ определить «контракт», которому должен соответствовать объект.

    Синтаксически это очень похоже на type, только без знака равно = после названия.

    !Сравнение интерфейса как чертежа (контракта) и объекта как реализации этого чертежа.

    Гибкая настройка полей: Optional и Readonly

    Независимо от того, используете вы type или interface, TypeScript предоставляет инструменты для тонкой настройки поведения свойств объекта.

    Необязательные свойства (Optional Properties)

    Иногда у объекта могут быть поля, наличие которых не гарантировано. Например, у пользователя может не быть указан номер телефона. Для этого используется знак вопроса ? после имени свойства.

    Если вы попытаетесь обратиться к person1.phone, TypeScript подскажет, что значение может быть undefined.

    Свойства только для чтения (Readonly)

    Некоторые данные не должны изменяться после создания объекта. Классический пример — id (идентификатор) записи в базе данных. Модификатор readonly запрещает перезапись поля.

    Это отличный способ защитить данные от случайных мутаций в коде.

    Композиция типов: Расширение и Пересечение

    Реальные данные часто строятся иерархически. У нас может быть Animal, а на его основе Dog и Cat. TypeScript позволяет нам не переписывать общие поля заново.

    Расширение интерфейсов (Extends)

    Интерфейсы используют ключевое слово extends для наследования свойств другого интерфейса. Это очень похоже на наследование классов в ООП.

    Пересечение типов (Intersection Types)

    Для type используется другой подход — пересечение (intersection), обозначаемое символом амперсанда &. Это словно «склеивание» двух типов в один.

    Примечание: Интерфейсы тоже могут расширять типы, а типы могут использовать пересечение с интерфейсами. Они довольно дружелюбны друг к другу.

    Interface vs Type: Великая битва

    Мы подошли к главному вопросу. Если они так похожи, зачем нужны оба? И что выбрать?

    Давайте рассмотрим ключевые различия.

    1. Объявление примитивов и Union-типов

    type более универсален. Он может описывать не только объекты, но и примитивы, объединения (unions) и кортежи. Интерфейсы умеют описывать только формы объектов.

    2. Слияние деклараций (Declaration Merging)

    Это уникальная фишка интерфейсов. Если вы объявите два интерфейса с одинаковым именем, TypeScript автоматически объединит их в один. С типами это вызовет ошибку «Duplicate identifier».

    Зачем это нужно? Это критически важно для разработчиков библиотек. Например, если вы хотите добавить новое свойство к глобальному объекту Window в браузере, вы можете просто написать интерфейс с таким же именем, и он расширит стандартный.

    3. Сообщения об ошибках

    Иногда TypeScript выдает более понятные ошибки при использовании интерфейсов, так как имя интерфейса сохраняется в сообщениях компилятора, тогда как сложные пересечения типов (TypeA & TypeB) могут раскрываться в длинную простыню свойств.

    Что выбрать: Best Practices

    В сообществе TypeScript нет единого жесткого правила, но есть общепринятые рекомендации:

  • Используйте interface по умолчанию для описания объектов. Особенно если вы пишете библиотеку или работаете над публичным API, который другие разработчики могут захотеть расширить.
  • Используйте type, когда вам нужны возможности, недоступные интерфейсам. Это объединения (Union Types), пересечения примитивов, кортежи или сложные манипуляции с типами (о которых мы поговорим в продвинутых уроках).
  • Будьте последовательны. Если в вашем проекте принято использовать type для всего — следуйте этому стилю.
  • Практический пример: Типизация ответа API

    Давайте соберем все вместе. Представьте, что мы получаем данные о статье с сервера. У нас есть автор, список тегов и сама статья.

    Этот код читаем, безопасен и легко расширяем. Если завтра нам понадобится добавить поле likes к статье, мы просто допишем его в интерфейс Article, и компилятор подскажет нам обновить данные во всех местах использования.

    Заключение

    Сегодня мы сделали огромный шаг вперед. Мы ушли от простых переменных к сложным структурам данных. Мы узнали:

    * Как создавать псевдонимы типов через type. * Как описывать контракты объектов через interface. * Как делать свойства необязательными (?) и неизменяемыми (readonly). * Как комбинировать типы через наследование (extends) и пересечение (&).

    Теперь вы можете описывать данные любой сложности. Но данные — это лишь статика. В программировании данные должны двигаться и изменяться. В следующей статье мы займемся функциями: узнаем, как типизировать аргументы, возвращаемые значения и стрелочные функции, чтобы наш код стал по-настоящему живым и безопасным.

    До встречи в следующем уроке!

    4. Функции и классы: типизация аргументов, возвращаемые значения и основы ООП

    Функции и классы: типизация аргументов, возвращаемые значения и основы ООП

    Приветствую вас в четвертой части курса «TypeScript для начинающих». В прошлых уроках мы научились описывать данные: от простых чисел и строк до сложных объектов и интерфейсов. Но данные сами по себе — это лишь статика. Чтобы приложение ожило, данные должны взаимодействовать, изменяться и передаваться. За это в программировании отвечают функции и классы.

    Сегодня мы превратим TypeScript из инструмента описания данных в инструмент построения архитектуры. Мы разберем, как сделать функции безопасными, зачем нужны модификаторы доступа в классах и как принципы Объектно-Ориентированного Программирования (ООП) реализуются в типизированном мире.

    Типизация функций

    Функции — это «глаголы» нашего кода. В JavaScript мы привыкли объявлять их свободно, не заботясь о том, что именно в них попадает. TypeScript вводит строгий контроль на входе и на выходе.

    Аргументы и возвращаемое значение

    Базовый синтаксис типизации функции выглядит так: мы указываем типы для каждого аргумента и тип того, что функция вернет после выполнения.

    Давайте разберем эту запись:

  • a: number, b: number — мы гарантируем, что внутри функции a и b всегда будут числами. Нам не нужно писать проверки вроде if (typeof a !== 'number').
  • : number после скобок — это тип возвращаемого значения. Если вы попытаетесь вернуть строку или не вернуть ничего, TypeScript выдаст ошибку.
  • !Визуализация принципа строгой типизации входных и выходных данных функции.

    Стрелочные функции

    Синтаксис стрелочных функций, ставший популярным в современном JavaScript, типизируется аналогично:

    Тип void

    Что делать, если функция ничего не возвращает? Например, она просто выводит сообщение в консоль или сохраняет данные в базу, не отдавая результат. В JavaScript такие функции технически возвращают undefined. В TypeScript для этого используется специальный тип void (пустота).

    Использование void ясно говорит другим разработчикам: «Не ждите результата от этой функции, просто вызовите её».

    Необязательные параметры и значения по умолчанию

    В JavaScript вы можете вызвать функцию с меньшим количеством аргументов, чем объявлено. В TypeScript это вызовет ошибку, если вы явно не разрешите такое поведение.

    Необязательные параметры (?)

    Чтобы сделать аргумент необязательным, поставьте знак вопроса ? после его имени. Важно: необязательные параметры всегда должны идти после обязательных.

    Если вы не объявите поле name перед конструктором, TypeScript выдаст ошибку Property 'name' does not exist on type 'User'.

    !Метафора класса как чертежа и объекта как реализации этого чертежа.

    Модификаторы доступа

    Это одна из самых важных возможностей TypeScript, которой нет в чистом JavaScript (до недавнего времени приватные поля в JS были в стадии эксперимента). Модификаторы позволяют управлять тем, кто может видеть и изменять данные внутри класса. Это основа принципа инкапсуляции.

    Существует три основных модификатора:

  • public (по умолчанию)
  • private
  • protected
  • Public

    Свойства и методы, помеченные как public, доступны отовсюду: внутри класса, в наследуемых классах и снаружи (через экземпляр объекта).

    Private

    Свойства с модификатором private доступны только внутри того класса, где они объявлены. Ни наследники, ни внешние объекты не могут к ним прикоснуться.

    Это защищает важные данные от случайного изменения извне.

    Protected

    Модификатор protected похож на private, но с одним отличием: он дает доступ к свойству классам-наследникам.

    Readonly в классах

    Мы уже встречали readonly в интерфейсах. В классах он работает так же: свойство можно установить только в момент объявления или внутри конструктора. После этого изменить его нельзя.

    Сокращенная инициализация в конструкторе

    Программисты ленивы, и писать this.name = name каждый раз утомительно. TypeScript предлагает элегантный синтаксис для объявления и инициализации свойств прямо в аргументах конструктора.

    Вместо этого:

    Можно написать так:

    Этот код делает то же самое: создает поля и присваивает им значения. Это стандарт де-факто в современном TypeScript-коде.

    Реализация интерфейсов (Implements)

    В прошлом уроке мы говорили об интерфейсах как о контрактах. Классы могут подписывать эти контракты с помощью ключевого слова implements.

    Это гарантирует, что класс реализует все методы и свойства, описанные в интерфейсе.

    Использование implements позволяет создавать взаимозаменяемые модули. Вашей программе может быть все равно, какой именно логгер используется, главное — он соответствует интерфейсу ILogger.

    Заключение

    Сегодня мы сделали большой шаг вперед. Мы научились:

  • Типизировать функции, аргументы и возвращаемые значения.
  • Использовать void для функций без результата.
  • Создавать классы и защищать данные с помощью private и protected.
  • Использовать сокращенный синтаксис конструктора.
  • Связывать классы и интерфейсы через implements.
  • Теперь ваш код не только хранит данные, но и безопасно ими манипулирует. В следующей статье мы перейдем к одной из самых мощных и сложных тем TypeScript — Дженерикам (Generics). Это позволит нам писать универсальный код, который работает с разными типами данных, сохраняя при этом строгую типизацию.

    До встречи в следующем уроке!

    5. Продвинутые возможности: введение в обобщения (Generics) и утилитарные типы

    Продвинутые возможности: введение в обобщения (Generics) и утилитарные типы

    Приветствую вас на пятом уроке курса «TypeScript для начинающих». Мы уже прошли долгий путь: настроили окружение, изучили базовые типы, научились структурировать данные с помощью интерфейсов и освоили классы. Казалось бы, у нас есть все инструменты для написания надежного кода. Однако, по мере роста сложности проектов, вы неизбежно столкнетесь с ситуацией, когда строгая типизация начинает... мешать.

    Представьте, что вы пишете функцию, которая должна работать с разными типами данных, но при этом сохранять их структуру. В обычном JavaScript это не проблема, но в TypeScript нам приходится выбирать: либо писать множество одинаковых функций для каждого типа (function forString, function forNumber), либо использовать тип any, который убивает всю безопасность.

    Сегодня мы познакомимся с решением этой дилеммы — Обобщениями (Generics). Это «золотой ключик» TypeScript, который позволяет создавать универсальные и переиспользуемые компоненты. Также мы рассмотрим Утилитарные типы — встроенные инструменты языка, которые избавят вас от рутинного написания лишнего кода.

    Проблема дублирования кода

    Давайте начнем с простого примера. Допустим, нам нужна функция, которая принимает аргумент и возвращает его же. В программировании такую функцию часто называют identity.

    Логика функций абсолютно одинакова, отличаются только типы. Если нам понадобится такая же функция для объекта User или массива чисел, нам придется копировать этот код снова и снова. Это нарушает принцип DRY (Don't Repeat Yourself — не повторяйся).

    Мы могли бы использовать any:

    Но здесь возникает проблема. Если мы передадим число, функция вернет any. Компилятор «забудет», что это было число. Мы потеряем автодополнение и проверку ошибок. Нам нужен способ сказать: «Я не знаю, какой тип придет, но я хочу, чтобы возвращаемый тип был точно таким же, как входящий».

    Что такое Generics (Обобщения)?

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

    В TypeScript принято использовать букву T (от слова Type) для обозначения этой переменной, хотя имя может быть любым.

    Синтаксис функции с дженериком

    Давайте перепишем нашу функцию identity с использованием обобщения:

    Разберем этот код по частям:

  • <T> после имени функции сообщает TypeScript, что мы объявляем переменную типа T. Теперь мы можем использовать T внутри функции.
  • arg: T означает, что аргумент будет иметь тип T.
  • : T означает, что функция вернет значение того же типа T.
  • !Визуализация принципа работы Generic: тип на входе сохраняется на выходе.

    Как использовать такую функцию?

    У нас есть два способа вызова обобщенной функции.

    Способ 1: Явное указание типа

    Мы передаем тип в угловых скобках, как аргумент.

    Способ 2: Вывод типов (Type Inference)

    Мы позволяем компилятору самому догадаться о типе на основе переданного значения. Это самый распространенный способ.

    Обобщения в интерфейсах и классах

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

    Generic Interfaces

    Представьте, что мы получаем ответ от сервера. Ответ всегда имеет поле status и поле data. Но data может быть чем угодно: списком пользователей, товаром или ошибкой.

    Благодаря дженерику ApiResponse, мы можем переиспользовать один интерфейс для любых данных, сохраняя строгую типизацию поля data.

    Generic Classes

    Классы тоже могут быть обобщенными. Это часто используется при создании коллекций, очередей или хранилищ.

    Ограничения обобщений (Constraints)

    Иногда мы хотим, чтобы наш дженерик работал не с любым типом, а только с теми типами, которые имеют определенные свойства.

    Допустим, мы хотим написать функцию, которая выводит в консоль длину аргумента.

    Тип T может быть числом или булевым значением, у которых нет свойства length. Нам нужно ограничить T. Для этого используется ключевое слово extends.

    Создадим интерфейс с требованием наличия длины:

    Теперь мы можем передавать строки и массивы (у них есть length), но не числа:

    Утилитарные типы (Utility Types)

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

    Рассмотрим самые популярные из них.

    1. Partial<T>

    Делает все свойства типа T необязательными (добавляет ?).

    Это полезно, например, при обновлении объекта, когда мы меняем только часть полей.

    2. Readonly<T>

    Делает все свойства типа T доступными только для чтения. Это защищает объект от изменений после создания.

    3. Pick<T, K>

    Создает новый тип, выбирая только указанные свойства K из типа T.

    Представьте, что у вас есть огромный интерфейс пользователя, но для отображения в списке вам нужны только имя и email.

    4. Omit<T, K>

    Работает противоположно Pick. Создает новый тип, исключая указанные свойства K из типа T.

    Это удобно, если вы хотите убрать чувствительные данные (например, пароль) перед отправкой объекта на клиент.

    5. Record<K, T>

    Используется для создания типа объекта (словаря), где ключи имеют тип K, а значения — тип T.

    Это гораздо лучше, чем писать any, когда вам нужен объект-маппинг.

    Заключение

    Сегодня мы перешли черту от новичка к уверенному пользователю TypeScript. Мы узнали:

  • Generics позволяют писать код, который гибок, как JavaScript, но безопасен, как строгий TypeScript.
  • Переменная типа <T> работает как «заполнитель», который заменяется на реальный тип в момент использования.
  • Constraints (extends) позволяют ограничивать дженерики, требуя наличия определенных свойств.
  • Utility Types (Partial, Pick, Omit и др.) — это готовые инструменты для манипуляции типами, которые экономят время и строки кода.
  • Обобщения — это фундамент большинства библиотек и фреймворков. Поняв их, вы сможете читать и понимать сложный код чужих проектов. В следующем уроке мы закрепим знания и поговорим о продвинутых техниках работы с типами и защитниках типов (Type Guards).

    Практикуйтесь, экспериментируйте с <T> и до встречи в следующем уроке!