Главная Профессиональный TypeScript. Разработка масштабируемых JavaScript приложений

Профессиональный TypeScript. Разработка масштабируемых JavaScript приложений

0 / 0
Насколько вам понравилась эта книга?
Какого качества скаченный файл?
Скачайте книгу, чтобы оценить ее качество
Какого качества скаченные файлы?
Любой программист, работающий с языком с динамической типизацией, подтвердит, что задача масштабирования кода невероятно сложна и требует большой команды инженеров. Вот почему Facebook, Google и Microsoft придумали статическую типизацию для динамически типизированного кода.

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

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

Борис Черный помогает разобраться со всеми нюансами и возможностями TypeScript, учит устранять ошибки и масштабировать код.
Категории:
Год:
2021
Издание:
1
Издательство:
Питер
Язык:
russian
Страницы:
352
ISBN 13:
9785446116515
Серия:
Бестселлеры O’Reilly
Файл:
PDF, 5,42 MB
Скачать (pdf, 5,42 MB)

Возможно Вас заинтересует Powered by Rec2Me

 

Ключевые слова

 
0 comments
 

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

Karl Marx/Friedrich Engels: briefwechsel, april 1856 bis dezember 1857.

Год:
1990
Язык:
german
Файл:
PDF, 6,11 MB
0 / 0
2

Karl Marx/Friedrich Engels: Briefwechsel September 1853 bis März 1856

Год:
1989
Язык:
german
Файл:
PDF, 4,54 MB
0 / 0
Boris Cherny

Beijing

Boston Farnham Sebastopol

Tokyo

Профессиональный

TypeScript
Разработка масштабируемых
JavaScript-приложений
Борис Черный

2021

ББК 32.988.02-018
УДК 004.738.5
Ч-49

Борис Черный
Ч-49

Профессиональный TypeScript. Разработка масштабируемых JavaScriptприложений. — СПб.: Питер, 2021. — 352 с.: ил. — (Серия «Бестселлеры
O’Reilly»).
ISBN 978-5-4461-1651-5
Любой программист, работающий с языком с динамической типизацией, подтвердит, что
задача масштабирования кода невероятно сложна и требует большой команды инженеров. Вот
почему Facebook, Google и Microsoft придумали статическую типизацию для динамически
типизированного кода.
Работая с любым языком программирования, мы отслеживаем исключения и вычитываем
код строку за строкой в поиске неисправности и способа ее устранения. TypeScript позволяет
автоматизировать эту неприятную часть процесса разработки.
TypeScript, в отличие от множества других типизированных языков, ориентирован на
прикладные задачи. Он вводит новые концепции, позволяющие выражать идеи более кратко
и точно, и легко создавать масштабируемые и безопасные современные приложения.
Борис Черный помогает разобраться со всеми нюансами и возможностями TypeScript, учит
устранять ошибки и масштабировать код.

16+ (В соответствии с Федеральным законом от 29 декабря 2010 г. № 436-ФЗ.)
ББК 32.988.02-018
УДК 004.738.5
Права на издание получены по соглашению с O’Reilly. Все права защищены. Никакая часть данной книги
не может быть воспроизведена в какой бы то ни было форме без письменного разрешения владельцев
авторских прав.
Информация, содержащаяся в данной книге, получена из источников, рассматриваемых издательством
как надежные. Тем не менее, имея в виду возможные человеческие или технические ошибки, издательство не может гарантировать абсолютную точность и полноту приводимых сведений и не несет ответственности за возможные ошибки, связанные с использованием книги.
Издательство не несет ответственности за доступность материалов, ссылки на которые вы можете найти в этой книге. На м; омент подготовки книги к изданию все ссылки на интернет-ресурсы были действующими.
ISBN 978-1492037651 англ.

ISBN 978-5-4461-1651-5

Authorized Russian translation of the English edition
of Programming TypeScript ISBN 9781492037651 © 2019 Boris Cherny.
This translation is published and sold by permission of O’Reilly Media, Inc.,
which owns or controls all rights to publish and sell the same.
© Перевод на русский язык
ООО Издательство «Питер», 2021
© Издание на русском языке,
оформление ООО Издательство «Питер», 2021
© Серия «Бестселлеры O’Reilly», 2021

Краткое содержание
Отзывы.............................................................................................................................................10
Пролог...............................................................................................................................................12
Глава 1. Вступление.....................................................................................................................18
Глава 2. TypeScript с высоты птичьего полета.................................................................21
Глава 3. Подробно о типах.......................................................................................................33
Глава 4. Функции..........................................................................................................................67
Глава 5. Классы и интерфейсы............................................................................................. 111
Глава 6. Продвинутые типы.................................................................................................. 145
Глава 7. Обработка ошибок.................................................................................................. 198
Глава 8. Асинхронное программирование, конкурентность
и параллельная обработка.................................................................................................... 215
Глава 9. Фронтенд- и бэкенд-фреймворки.................................................................... 247
Глава 10. Пространства имен и модули.......................................................................... 265
Глава 11. Взаимодействие с JavaScript............................................................................ 283
Глава 12. Создание и запуск TypeScript........................................................................... 306
Глава 13. Итоги........................................................................................................................... 327
Приложение A. Операторы типов.................................................................................... 329
Приложение Б. Утилиты типов........................................................................................... 330
Приложение В. Область действия деклараций.......................................................... 331
Приложение Г. Правила написания файлов деклараций
для сторонних модулей JavaScript..................................................................................... 333
Приложение Д. Директивы с тремя слешами............................................................. 342
Приложение Е. Флаги безопасности компилятора TSC ......................................... 344
Приложение Ж. TSX................................................................................................................. 346
Об авторе...................................................................................................................................... 349
Об обложке....................................................................................................... 350

Оглавление
Отзывы....................................................................................................... 10
Пролог........................................................................................................ 12
Структура книги...........................................................................................12
Стиль...........................................................................................................13
Использование примеров кода.....................................................................15
Благодарности..............................................................................................16
От издательства...........................................................................................17
Глава 1. Вступление.........................................................................................18
Глава 2. TypeScript с высоты птичьего полета..................................................21
Компилятор..................................................................................................21
Система типов..............................................................................................23
Настройка редактора кода............................................................................27
index.ts.........................................................................................................30
Упражнения к главе 2...................................................................................32
Глава 3. Подробно о типах...............................................................................33
О типах........................................................................................................34
Типы от а до я..............................................................................................35
Итоги............................................................................................................65
Упражнения к главе 3...................................................................................66
Глава 4. Функции.............................................................................................67
Объявление и вызов функций......................................................................67
Полиморфизм...............................................................................................90
Разработка на основе типов....................................................................... 108
Итоги.......................................................................................................... 109
Упражнения к главе 4................................................................................. 110

Оглавление   7

Глава 5. Классы и интерфейсы....................................................................... 111
Классы и наследование.............................................................................. 111
super.......................................................................................................... 117
Использование this в качестве возвращаемого типа................................... 117
Интерфейсы............................................................................................... 119
Классы структурно типизированы............................................................... 126
Классы объявляют и значения, и типы....................................................... 127
Полиморфизм............................................................................................. 131
Примеси..................................................................................................... 132
Декораторы................................................................................................ 135
Имитация финальных классов.................................................................... 138
Паттерны проектирования.......................................................................... 139
Итоги.......................................................................................................... 142
Упражнения к главе 5................................................................................. 144
Глава 6. Продвинутые типы........................................................................... 145
Связи между типами................................................................................... 145
Тотальность............................................................................................... 165
Продвинутые типы объектов...................................................................... 167
Продвинутые функциональные типы.......................................................... 177
Условные типы........................................................................................... 180
Запасные решения..................................................................................... 185
Имитация номинальных типов.................................................................... 191
Безопасное расширение прототипа............................................................ 193
Итоги.......................................................................................................... 196
Упражнения к главе 6................................................................................. 197
Глава 7. Обработка ошибок............................................................................ 198
Возврат null................................................................................................ 199
Выбрасывание исключений........................................................................ 200
Возврат исключений................................................................................... 203

8  

Оглавление

Тип Option.................................................................................................. 205
Итоги.......................................................................................................... 213
Упражнение к главе 7................................................................................. 214
Глава 8. Асинхронное программирование, конкурентность
и параллельная обработка.............................................................................. 215
Цикл событий............................................................................................. 216
Работа с обратными вызовами.................................................................... 218
Промисы как здоровая альтернатива.......................................................... 221
async и await.............................................................................................. 227
Async-потоки.............................................................................................. 228
Типобезопасная многопоточность.............................................................. 231
Итоги.......................................................................................................... 245
Упражнения к главе 8................................................................................. 246
Глава 9. Фронтенд- и бэкенд-фреймворки...................................................... 247
Фронтенд-фреймворки............................................................................... 247
Типобезопасные API................................................................................... 260
Бэкенд-фреймворки.................................................................................... 262
Итоги.......................................................................................................... 264
Глава 10. Пространства имен и модули.......................................................... 265
Краткая история модулей JavaScript........................................................... 266
import, export.............................................................................................. 269
Пространства имен..................................................................................... 274
Слияние деклараций.................................................................................. 279
Итоги.......................................................................................................... 281
Упражнение к главе 10............................................................................... 282
Глава 11. Взаимодействие с JavaScript........................................................... 283
Декларации типов...................................................................................... 283
Поэтапная миграция из JavaScript в TypeScript . ......................................... 292
Поиск типов для JavaScript......................................................................... 298

Оглавление   9

Использование стороннего кода JavaScript................................................. 301
Итоги.......................................................................................................... 305
Глава 12. Создание и запуск TypeScript.......................................................... 306
Создание проекта в TypeScript ................................................................... 306
Запуск TypeScript на сервере...................................................................... 317
Запуск TypeScript в браузере...................................................................... 318
Публикация TypeScript-кода на NPM............................................................ 321
Директивы с тремя слешами....................................................................... 322
Итоги.......................................................................................................... 326
Глава 13. Итоги.............................................................................................. 327
Приложение A. Операторы типов................................................................. 329
Приложение Б. Утилиты типов...................................................................... 330
Приложение В. Область действия деклараций.............................................. 331
Генерирует ли декларация тип................................................................... 331
Допускает ли декларация слияние............................................................. 331
Приложение Г. Правила написания файлов деклараций
для сторонних модулей JavaScript.................................................................... 333
Типы экспорта............................................................................................ 334
Расширение модуля.................................................................................... 337
Приложение Д. Директивы с тремя слешами................................................ 342
Внутренние директивы............................................................................... 343
Нежелательные директивы......................................................................... 343
Приложение Е. Флаги безопасности компилятора TSC ................................. 344
Приложение Ж. TSX..................................................................................... 346
Об авторе...................................................................................................... 349
Об обложке............................................................................................... 350

Отзывы
Отличная книга для углубленного изучения TypeScript. Она
демонстрирует все преимущества использования системы
типов и помогает обрести уверенность при работе с JavaScript.
Минко Гечев,
инженер команды Angular в Google

Книга «Профессиональный TypeScript. Разработка масштабируемых JavaScript-приложений» помогла мне быстро
освоить инструменты и внутреннее устройство этого языка.
Она дала ответы на все мои вопросы с помощью реальных
примеров. Глава «Продвинутые типы» сломала терминологические барьеры и показала, как TypeScript позволяет создать
безопасный и удобный код.
Шон Гров,
сооснователь OneGraph

Борис создал обширное руководство по TypeScript. Прочтите
его вдоль и поперек. А затем еще разочек.
Блейк Эмбри, инженер в Opendoor,
автор TypeScript Node and Typings
(«Типизации и Node в TypeScript»)

Посвящается Саше и Михаилу.
Возможно, и они однажды полюбят типы.

Пролог
Эта книга предназначена для программистов всех направлений: JavaScriptинженеров, C#-разработчиков, сторонников Java, любителей Python,
Ruby и Haskell. На каком бы языке вы ни писали, если вам известны
функции, переменные, классы и связанные с ними ошибки, то эта книга
для вас. Наличие некоторого опыта в JavaScript, включая базовые знания
об объектной модели документа (DOM) и обмене информацией по сети,
поможет вам освоить представленный материал. Хоть мы и не сильно
углубляемся в эти понятия, на них основаны приведенные примеры.
Работая с любым языком программирования, мы отслеживаем исключения и вычитываем код строку за строкой в поиске неисправности
и способа ее устранения. TypeScript позволяет автоматизировать эту
неприятную часть процесса разработки.
Если раньше вы не встречались со статической типизацией, то узнаете
о типах из этой книги. Я расскажу, как с их помощью снизить риск программного сбоя, улучшить документирование кода и масштабировать его
для большего числа пользователей, инженеров и серверов. Без сложных
терминов я объясню материал, подключая вашу интуицию и память,
и в этом мне помогут примеры.
TypeScript, в отличие от множества других типизированных языков, преимущественно практический. Он вводит новые концепции, позволяющие
выражать идеи более кратко и точно и играючи создавать современные
приложения безопасным способом.

Структура книги
Я постарался передать вам теоретическое понимание работы TypeScript
и достаточное количество практических советов по написанию кода.
TypeScript — практический язык, поэтому в книге теория и практика
в основ­ном дополняют друг друга, но в первых двух главах преимущественно освещается теория, а ближе к концу представлена только практика.

Стиль   13

Мы рассмотрим такие основы, как компилятор, модуль проверки типов
и сами типы. Далее обсудим их разновидности и операторы, после чего
перейдем к углубленным темам, таким как особенности системы типов,
обработка ошибок и асинхронное программирование. В завершение
я расскажу, как использовать TypeScript с вашими любимыми фреймворками (фронтенд и бэкенд), производить миграцию существующего
JavaScript-проекта в TypeScript и запускать TypeScript-приложение
в продакшене.
Большинство глав завершаются набором упражнений. Попробуйте выполнить их самостоятельно, чтобы лучше усвоить материал. Ответы к ним
доступны по адресу https://github.com/bcherny/programming-typescript-answers.

Стиль
В коде я старался придерживаться единого стиля, поэтому отмечу, какие
мои личные предпочтения он содержит.
‰‰Точка

с запятой только при необходимости.

‰‰Отступы

двумя пробелами.

‰‰Короткие имена переменных вроде a, f или _ там, где программа явля-

ется фрагментом кода или ее структура важнее деталей.
Я считаю, что некоторых аспектов этого стиля стоит придерживаться
и вам. К ним относятся:
‰‰Использование

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

‰‰Использование

оператора расширения spread для длительного сохранения неизменности структур данных1.

1

Если у вас нет опыта работы в JavaScript, то вот пример: чтобы объекту o добавить
свойство k со значением 3, можно либо изменить o напрямую — o.k = 3, либо применить это изменение к o, создав тем самым новый объект — let p = {...o, k: 3}.

14  

Пролог

‰‰Привычка

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

‰‰Стремление сохранить код переиспользуемым и обобщенным (см. раз-

дел «Полиморфизм» на с. 90).
Конечно, все это не ново, но для TypeScript имеет особую актуальность.
Его встроенный низкоуровневый компилятор, поддержка типов readonly (только для чтения), мощный интерфейс типов, глубокая поддержка
полиморфизма и полностью структурная система типов позволяют добиться хорошего стиля, в то время как сам язык остается выразительным
и верным для лежащего в его основе JavaScript.
Еще пара заметок до перехода к основному материалу.
JavaScript не предоставляет указатели и ссылки. Вместо них используются значения и ссылочные типы. Значения являются неизменными и включают такие элементы, как strings (строки), numbers (числа)
и booleans (логические значения), в то время как ссылочные типы часто
указывают на изменяемые структуры данных вроде массивов, объектов
и функций. Когда я использую слово «значение», то обычно имею в виду
либо значение JavaScript, либо ссылку.
И последнее. Написание идеального TypeScript-кода затрудняется
взаимодействием с JavaScript, некорректно типизированными сторонними библиотеками и наследованным кодом. Также ему мешает
спешка. В книге я буду часто советовать вам избегать компромиссов.
В реальности достаточную корректность кода будете определять только
вы и ваша команда.

Типографские соглашения
Ниже приведен список используемых обозначений.
Курсив
Используется для обозначения новых терминов.

Использование примеров кода   15

Моноширинный

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

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

Так обозначаются примечания общего характера.

Так выделяются советы и предложения.

Так обозначаются предупреждения и предостережения.

Использование примеров кода
Вспомогательный материал (примеры кода, упражнения и пр.) доступен
для загрузки по адресу: https://github.com/bcherny/programming-typescript-answers.
В общем случае все примеры кода из этой книги вы можете использовать в своих программах и в документации. Вам не нужно обращаться
в издательство за разрешением, если вы не собираетесь воспроизводить
существенные части программного кода. Например, если вы разрабаты-

16  

Пролог

ваете программу и используете в ней несколько отрывков программного
кода из книги, вам не нужно обращаться за разрешением. Однако в случае продажи или распространения компакт-дисков с примерами из этой
книги вам следует получить разрешение от издательства O’Reilly. Если
вы отвечаете на вопросы, цитируя данную книгу или примеры из нее,
получение разрешения не требуется. Но при включении существенных
объемов программного кода примеров из этой книги в вашу документацию
вам необходимо будет получить разрешение издательства.
За получением разрешения на использование значительных объемов
программного кода примеров из этой книги обращайтесь по адресу
permissions@oreilly.com.

Благодарности
Написание этой книги заняло год и потребовало множества бессонных
ночей, ранних подъемов и потерянных выходных. В ее основе лежат
многолетние наработки — сниппеты и заметки, собранные воедино, доработанные и обогащенные теорией.
Хочу поблагодарить O’Reilly за предоставленную возможность работы над
книгой и лично редактора Анжелу Руфино за поддержку на протяжении
всего процесса. Благодарю Ника Нэнса за помощь в создании раздела
«Типобезопасные API» и Шьяма Шешадри за помощь с подразделом
«Angular» (см. с. 256). Спасибо моим научным редакторам: Даниэлю
Розенвассеру из команды TypeScript, который провел огромное количество времени, вычитывая рукопись и знакомя меня с нюансами системы
типов, а также Джонатану Кримеру, Якову Файну, Полу Байингу и Рэйчел
Хэд за технические правки и обратную связь. Спасибо моей семье — Лизе
и Илье, Вадиму, Розе и Алику, Фаине и Иосифу — за вдохновение на
реализацию этого проекта.
Самую большую благодарность выражаю Саре Гилфорд, которая поддерживала меня на протяжении всего процесса написания, даже когда отменялись планы на вечер и выходные или я внезапно начинал рассуждать
на тему различных деталей системы типов. Я бесконечно благодарен ей
за поддержку, без которой точно не справился бы.

От издательства   17

От издательства
Ваши замечания, предложения, вопросы отправляйте по адресу comp@
piter.com (издательство «Питер», компьютерная редакция).
Мы будем рады узнать ваше мнение!
На веб-сайте издательства www.piter.com вы найдете подробную информацию о наших книгах.

ГЛАВА 1

Вступление
Итак, вы купили книгу о TypeScript. Зачем она нужна?
Вероятно, затем, что вам изрядно надоели странные ошибки в JavaScript
вроде cannot read property… of undefined («невозможно прочесть свойство…
принадлежащее undefined»). Или вы слышали, что TypeScript помогает
масштабировать код, и решили изучить этот вопрос. Или вы работаете в C#
и подумываете о переходе на JavaScript. А может, занимаетесь функциональным программированием и настало время повысить свой уровень. Или
эту книгу вы получили в подарок на Новый год от босса, который устал от
проблем, вызванных вашим кодом (если я перегибаю, остановите меня).
TypeScript — это язык будущих веб- и мобильных приложений, проектов
NodeJS и IoT (систем интернет-управления устройствами). Он позволяет
создавать более безопасные программы, обеспечивать их документацией,
полезной и вам, и будущим инженерам, поддерживает безболезненный
рефакторинг, а также избавляет от необходимости проводить половину
модульных, или юнит-тестов. (Каких еще модульных тестов?) TypeScript
может удвоить вашу продуктивность и даже устроить свидание с той
милой бариста из кафе напротив.
Но прежде, чем спешить к ней, разберемся с упомянутыми преимуществами. Что конкретно я имею в виду, когда говорю «более безопасные»?
Конечно, речь идет о безопасности типов.

БЕЗОПАСНОСТЬ ТИПОВ
Использование типов для предотвращения неверного
поведения программ1.

1

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

Глава 1. Вступление   19

Вот несколько примеров неверного поведения кода:
‰‰Перемножение
‰‰Вызов

числа и списка.

функции со списком строк, когда требуется список объектов.

‰‰Вызов

метода для объекта, когда фактически данный метод не существует в этом объекте.

‰‰Импортирование

модуля, который недавно был перемещен.

Некоторые языки программирования стремятся извлечь пользу из подобных ошибок — стараются понять, что вы имели в виду. Возьмем,
к примеру, такой JavaScript-код:
3 + []

// Вычисляется как строка "3"

let obj = {}
obj.foo

// Вычисляется как undefined

function a(b) {
return b/2
}
a("z")

// Вычисляется как NaN

JavaScript делает все возможное, чтобы избежать исключения. Оказывается ли он полезен? Определенно да. Но можете ли вы при этом быстро
и точно обнаруживать баги? Скорее всего, нет.
А теперь представьте, что JavaScript выдает больше исключений вместо
попытки извлечь максимум из того, что мы ему дали. Тогда получим подобную реакцию:
3 + []

// Ошибка: вы точно хотели добавить число и массив?

let obj = {}
obj.foo

// Ошибка: вы забыли определить свойство "foo" в obj.

function a(b) {
return b/2
}
a("z")

// Ошибка: функция "a" ожидает число,
// но вы передали ей строку.

20  

Глава 1. Вступление

Не поймите меня превратно: попытка исправить за нас наши же ошибки — это приятная особенность языка программирования (ох, если бы
она работала не только для программ). Но в JavaScript она создает разрыв
между моментом, когда вы допускаете ошибку, и временем ее обнаружения. Доходит до того, что вы узнаете об ошибке от кого-то другого.
Когда именно JavaScript сообщает об ошибке?
Или при запуске программы для тестирования в браузере, или при посещении сайта пользователем, или в начале модульного тестирования.
Если вы пишете множество модульных и полных тестов, проверяя код на
работоспособность, то можно рассчитывать на то, что вы увидите ошибки
раньше пользователей. Но что, если вы этого не делаете?
Вот здесь-то и появляется TypeScript. И самое крутое в том, что он выдает
сообщения об ошибках в момент их появления (во время типизации)
в вашем редакторе. Посмотрим, что он скажет по предыдущему примеру:
3 + []

// Ошибка TS2365: оператор '+' не может быть применен
// для типов '3' и 'never[]'.

let obj = {}
obj.foo
// Ошибка TS2339: свойство 'foo' не существует в типе '{}'.
function a(b: number) {
return b / 2
}
a("z")
// Ошибка TS2345: аргумент типа '"z"' не может быть
// присвоен параметру типа 'number'.

Он не только устраняет целый класс багов, связанных с типами, но и изменяет подход к написанию кода. Вы начнете делать наброски программы
на уровне типов еще до начала ее наполнения на уровне значений1, обдумывать пограничные случаи уже во время ее проектирования. В итоге
вы станете проектировать более простые, быстрые, понятные и легко
обслуживаемые программы.
Если вы готовы начать путешествие, тогда приступим!

1

Если вы не совсем понимаете, что в данном случае значит «уровень значений»,
не переживайте. В следующих главах мы раскроем это понятие.

ГЛАВА 2

TypeScript с высоты
птичьего полета
В нескольких следующих главах мы рассмотрим TypeScript, работу его
компилятора (TSC), а также функции и паттерны, которые вы можете
разрабатывать.

Компилятор
Ваше представление о программах во многом зависит от того, с какими
языками программирования вы работали ранее. TypeScript отличается
от большинства основных языков.
Программы — это файлы, содержащие прописанный вами текст. Специальная программа — компилятор — считывает и преобразует ваш текст
в абстрактное синтаксическое дерево (АСД). Оно представляет собой
структуру данных, игнорирующую пустые области, комментарии и ваше
ценное мнение о пробелах или табуляции. Затем компилятор преобразует
АСД в низкоуровневую форму — байт-код, который можно запустить
в среде выполнения и получить результат. Итак, когда вы запускаете
программу, фактически вы просите среду выполнения считать байт-код,
сгенерированный компилятором на основе АСД, полученного из исходного кода. Детали этого процесса могут отличаться, но для большинства
языков он выглядит так:
1. Программа преобразуется в АСД.
2. АСД компилируется в байт-код.
3. Байт-код считывается средой выполнения.
Особенность TypeScript в том, что вместо компиляции прямо в байт-код он
компилирует в код JavaScript. Затем вы просто запускаете его в браузере,

22  

Глава 2. TypeScript с высоты птичьего полета

с NodeJS или вручную, с помощью бумаги и ручки (вариант для тех, кто
читает книгу во время восстания машин).
«Причем здесь безопасность кода?» — спросите вы.
Отличный вопрос. Я пропустил важный этап: после создания АСД компилятор проверяет типы.

МОДУЛЬ ПРОВЕРКИ ТИПОВ
Специальная программа, определяющая типобезопасность кода.

В проверке типов заключена магия TypeScript. С ее помощью он убеждается, что программа работает так, как вы ожидаете, и что приятная бариста из
кафе напротив перезвонит вам (на самом деле она сейчас просто занята).
Итак, если мы добавим проверку типов и преобразование в JavaScript, то
процесс компиляции TypeScript будет выглядеть примерно так (рис. 2.1).
1. Код на TS -> TypeScript АСД
2. АСД проверяется проверкой типов
3. TypeScript АСД -> Код на JS
1. Код на JS -> JavaScript АСД
2. АСД -> Байт-код
3. Байт-код оценивается
по времени выполнения

Рис. 2.1. Компиляция и запуск TypeScript

Шаги 1–3 производятся компилятором, а шаги 4–6 — средой выполнения JavaScript, находящейся в вашем браузере, или NodeJS, или любым
другим JavaScript-движком.
JavaScript-компиляторы и среды выполнения, как правило, представляют собой единую программу, называемую движком. Будучи
программистом, с ним вы и будете взаимодействовать. Так работают V8 (движок, лежащий в основе NodeJS, Chrome и Opera),
SpiderMonkey (Firefox), JSCore (Safari) и Chakra (Edge). Именно поэтому JavaScript и называют интерпретируемым языком.

Система типов   23

В течение всего процесса шаги 1–2 используют типы программы, а шаг 3
уже этого не делает. Стоит еще раз повториться: когда TSC компилирует код в JavaScript, он не будет смотреть на типы. Это означает, что
типы никогда не смогут повлиять на сгенерированный вывод и будут
использованы только для проверки типов. Эта особенность позволяет
безопасно с ними экспериментировать — обновлять и улучшать их без
риска сломать приложение.

Система типов
В современных языках реализованы различные системы типов.

СИСТЕМА ТИПОВ
Набор правил, используемых модулем проверки типов
для присвоения типов программе.

Главным образом системы типов делятся на два вида: в одних вы должны сообщать компилятору тип каждого элемента посредством явного
синтаксиса, другие выводят типы автоматически. Оба вида имеют как
плюсы, так и минусы1.
TypeScript создан на стыке этих двух видов: вы можете явно аннотировать
типы либо позволить TypeScript делать их вывод за вас.
Для явного объявления типов используются аннотации, которые сообщают TypeScript, что такое-то значение имеет такой-то тип. Давайте
взглянем на несколько примеров (комментарии в соответствующих
строках указывают типы, выведенные TypeScript):
let a: number = 1
let b: string = 'hello'
let c: boolean[] = [true, false]

1

// a является number
// b является string
// c является массивом booleans

JavaScript, Python и Ruby выводят типы в среде выполнения. Haskell и OCaml делают вывод типов и проверяют недостающие типы в процессе компиляции. Scala
и TypeScript иногда требуют явного указания типов, вывод же и проверку остальных они производят при компиляции. Java и C нуждаются в явных аннотациях
практически для всего, что проверяют в среде выполнения.

24  

Глава 2. TypeScript с высоты птичьего полета

Если вы хотите, чтобы TypeScript вывел за вас типы, то просто не прописывайте их:
let a = 1
let b = 'hello'
let c = [true, false]

// a является number
// b является string
// c является массивом booleans

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

TypeScript vs JavaScript
Давайте углубимся в систему типов TypeScript и произведем сравнение
с ее аналогом в JavaScript (табл. 2.1). Правильное понимание разницы
является ключом для построения образной модели функционирования
TypeScript.
Таблица 2.1. Сравнение систем типов JavaScript и TypeScript
Система типов

JavaScript

TypeScript

Как связываются типы

Динамически

Статически

Конвертируются ли типы
автоматически

Да

Нет (в основном)

Когда проверяются типы

Во время выполнения

Во время компиляции

Когда вскрываются ошибки

Во время выполнения (в основном)

Во время компиляции (в основном)

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

Система типов   25

в нетипизированной программе TypeScript может вывести часть типов
и обнаружить малую долю ошибок; остальные же ошибки могут просочиться к конечным пользователям.
Постепенная типизация очень полезна при миграции наследованных
баз кода из нетипизированного JavaScript в типизированный TypeScript
(см. раздел «Поэтапная миграция из JavaScript в TypeScript» на с. 292),
но пока вы не достигнете середины процесса миграции, стремитесь
к 100%-ной типизации кода. Именно этот подход используется в книге,
за исключением обозначенных случаев.

Конвертируются ли типы автоматически
JavaScript является слабо типизированным языком, поэтому если вы
произведете недопустимое сложение, например числа и массива (как
в главе 1), то он применит множество правил для выяснения вашего намерения, чтобы выдать наилучший результат. Рассмотрим пример того,
как JavaScript определяет значение 3 + [1]:
1. JavaScript замечает, что 3 является числом, а [1] — массивом.
2. Увидев +, он предполагает, что вы хотите произвести их конкатенацию.
3. Он неявно преобразует 3 в строку, создавая "3".
4. Он также неявно преобразует в строку [1], создавая "1".
5. Производит конкатенацию этих результатов: "31".
Мы могли бы сделать это и более явно (так JavaScript избежит шагов 1,
3 и 4):
3 + [1];
(3).toString() + [1].toString()

// вычисляется как "31"
// вычисляется как "31"

В то время как JavaScript старается произвести умные преобразования
в стремлении вам помочь, TypeScript начинает указывать на ошибки, как
только вы делаете что-либо неверно. Если вы запустите тот же код через
TSC, то получите ошибку:
3 + [1];

(3).toString() + [1].toString()

//
//
//
//

Ошибка TS2365: оператор '+'
не может быть применен к типам '3'
и 'number[]'.
вычисляется как "31".

26  

Глава 2. TypeScript с высоты птичьего полета

Как только вы делаете нечто, что выглядит неправильным, TypeScript на
это указывает. Если же вы делаете свои намерения явными, он перестает
препятствовать. В таком поведении есть смысл: кто бы, находясь в здравом уме, стал складывать число и массив, ожидая в результате получить
строку? (Конечно, не считая JavaScript-ведьмы Бавморды, которая пишет
код при свечах в гараже, где устроился ваш стартап.)
Неявное преобразование, которое производит JavaScript, может серьезно
усложнить обнаружение источника ошибок, особенно при масштабировании проекта крупной командой разработчиков, где каждый инженер
будет вынужден понять неявные предположения, формулируемые кодом.
Вывод: если вам необходимо конвертировать типы, делайте это явно.

Когда проверяются типы
В большинстве случаев JavaScript не интересует, какие вы предоставляете
ему типы, но при этом он старается преобразовать то, что вы предоставили,
в то, что он сам ожидает.
TypeScript же, напротив, проверяет типы в процессе компиляции (шаг 2
из списка в начале главы), поэтому вам не нужно запускать код, чтобы
увидеть ошибку из предыдущего примера. TypeScript статически анализирует код на наличие подобных ошибок и показывает их еще до запуска.
Если код не проходит компиляцию, то это явный признак присутствия
ошибки, которую нужно исправить до запуска кода.
Рис. 2.2 показывает, что происходит при типизации последнего примера кода в VSCode (предпочтенный мной редактор).

Рис. 2.2. VSCode сообщает об ошибке типа

Если в вашем редакторе установлено хорошее расширение TypeScript, то
ошибка будет подчеркнута красной волнистой линией при типизации
кода. Это существенно ускорит цикл обратной связи между написанием
кода, осознанием допущенной ошибки и обновлением кода с исправлением.

Настройка редактора кода   27

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

Настройка редактора кода
Теперь, когда вы уже имеете представление о работе компилятора Type­
Script и его системе типов, мы можем переходить к настройке редактора
и погружению в сам процесс написания кода.
Начните с выбора редактора и его загрузки. Лично мне нравится VSCode,
потому что в нем редактирование TypeScript-кода особенно удобно, но
вы можете рассмотреть Sublime Text, Atom, Vim, WebStorm и др. Как правило, инженеры весьма требовательны к ИСР (интегрированной среде
разработки), поэтому оставлю этот выбор за вами. Если же вы желаете
использовать VSCode, то для его установки просто следуйте инструкциям
на сайте https://code.visualstudio.com/.
1

2

Без сомнения, JavaScript вскрывает синтаксические ошибки и способен обнаружить
некоторые баги (вроде множественных деклараций const с одним именем и в одном
диапазоне) после считывания программы, но до ее запуска. Если вы считываете
JavaScript-код в процессе сборки (например, в Babel), то эти ошибки тоже обнаружатся.
Инкрементно компилируемые языки позволяют при внесении небольших изменений произвести быструю перекомпиляцию вместо перекомпилирования всей
программы (включая незатронутые ее части).

28  

Глава 2. TypeScript с высоты птичьего полета

Сам по себе TSC — это приложение командной строки, написанное
в TypeScript1, поэтому для его запуска понадобится NodeJS. Для его
установки также есть инструкции на официальном сайте.
NodeJS поставляется с NPM — пакетным менеджером, который нужен
для управления зависимостями проекта и сборкой. Мы начнем с его
использования для установки TSC и TSLint (линтер для TypeScript).
Откройте терминал и создайте новый каталог, затем инициализируйте
в нем новый проект NPM:
# Создание каталога
mkdir chapter-2
cd chapter-2
# Инициализация нового проекта NPM (следуйте инструкциям)
npm init
# Установка TSC, TSLint и деклараций типов для NodeJS
npm install --save-dev typescript tslint @types/node

tsconfig.json
Каждый TypeScript-проект должен содержать в корневой директории
файл tsconfig.json. По нему TypeScript ориентируется, какие файлы и в какую директорию компилировать, а также выясняет, какая версия JavaScript
требуется на выходе.
Создайте файл с именем tsconfig.json в корневом каталоге (touch tsconfig.json)2,
затем откройте его в редакторе и внесите следующее:
{
"compilerOptions": {
"lib": ["es2015"],
"module": "commonjs",
"outDir": "dist",
"sourceMap": true,
1

2

Таким образом, TSC попадает в разряд так называемых компиляторов с самостоятельным хостингом, которые компилируют свой собственный исходный код.
В данном примере мы создаем tsconfig.json вручную. В дальнейшем вы можете использовать встроенную команду TSC, чтобы генерировать этот файл автоматически:
./node_modules/.bin/tsc --init.

Настройка редактора кода   29

"strict": true,
"target": "es2015"
},
"include": [
"src"
]
}

Кратко пройдемся по значениям некоторых из перечисленных опций
(табл. 2.2).
Таблица 2.2. Опции tsconfig.json
Опция

Описание

include

В каких каталогах TSC должен искать файлы TypeScript?

lib

Наличие каких API в вашей среде разработки должен предполагать TSC? Это касается
также таких элементов, как Function.prototype.bind в ES5, Object.assign в ES2015 и document
DOM.querySelector?

module

В какую модульную систему должен производить компиляцию TSC (CommonJS, SystemJS,
ES2015 и пр.)?

outDir

В какой каталог TSC должен помещать сгенерированный JavaScript-код?

strict

Как производить максимально строгую проверку кода и соблюдать правильную типизацию?
Мы будем использовать ее во всех примерах. Активируйте ее в своем проекте

target

В какую версию JavaScript нужно компилировать код (ES3, ES5, ES2015, ES2016 и пр.)?

Это лишь несколько из десятков опций, которые поддерживает tsconfig.json,
причем постоянно добавляются новые. На деле вы не будете часто их
менять, за исключением набора номера в настройках module и target при
переключении на новый бандлер модулей, добавления "dom" к lib при
написании TypeScript для браузера (см. главу 12) или изменения уровня
строгости проверки кода при миграции JavaScript-проекта в TypeScript
(см. раздел «Поэтапная миграция из JavaScript в TypeScript» на с. 292).
Самый полный и актуальный список доступных опций находится в документации на сайте TypeScript (http://bit.ly/2JWfsgY).
Использование файла tsconfig.json для конфигурирования TSC очень удобно, поскольку позволяет проверять конфигурацию в системе контроля
версий. Вы также можете установить бˆольшую часть опций TSC через
командную строку: запустите ./node_modules/.bin/tsc --help для вывода их
списка.

30  

Глава 2. TypeScript с высоты птичьего полета

tslint.json
В вашем проекте также должен присутствовать файл tslint.json, содержащий конфигурацию TSLint, определяющую необходимую вам стилистику
кода (табуляции, пробелы и пр.).
Использование TSLint не обязательно, но для любого проекта Type­
Script настоятельно рекомендуется придерживаться определенной
стилистики, чтобы избежать споров с коллегами во время код-ревью.

Следующая команда сгенерирует файл tslint.json со стандартной конфигурацией TSLint:
./node_modules/.bin/tslint --init

Далее вы можете переписать ее согласно своему стилю. Например, мой
файл tslint.json выглядит так:
{
"defaultSeverity": "error",
"extends": [
"tslint:recommended"
],
"rules": {
"semicolon": false,
"trailing-comma": false
}
}

Полный список доступных правил содержится в документации TSLint.
Вы также можете добавлять пользовательские правила или устанавливать дополнительные настройки (как для ReactJS: https://www.npmjs.com/
package/tslint-react).

index.ts
Теперь, когда вы настроили tsconfig.json и tslint.json, создайте каталог src,
содержащий ваш первый файл TypeScript:
mkdir src
touch src/index.ts

index.ts   31

Структура каталога проекта должна выглядеть так:
chapter-2/
├──node_modules/
├──src/
│ └──index.ts
├──package.json
├──tsconfig.json
└──tslint.json

Откройте src/index.ts в редакторе и введите следующий код:
console.log('Hello TypeScript!')

Затем скомпилируйте и запустите TypeScript-код:
# Скомпилируйте код с помощью TSC
./node_modules/.bin/tsc
# Запустите код с помощью NodeJS
node ./dist/index.js

Если вы следовали перечисленным шагам, то код запустится и в консоли
вы увидите запись:
Hello TypeScript!

Вот и все — вы только что настроили и запустили ваш первый TypeScript
с нуля. Отличная работа!
Для первого запуска проекта TypeScript с нуля я привел пошаговую
инструкцию. В дальнейшем для ускорения этого процесса можете
использовать пару сокращений.
yy Установите ts-node (https://www.npmjs.com/package/ts-node) и используйте его для компиляции и запуска программы посредством всего
одной команды.
yy Используйте инструмент автоматической генерации typescript-nodestarter (https://github.com/Microsoft/TypeScript-Node-Starter) для быстрого
создания структуры каталога.

32  

Глава 2. TypeScript с высоты птичьего полета

Упражнения к главе 2
Теперь, когда ваша среда настроена, откройте src/index.ts в редакторе
и введите следующий код:
let a = 1 + 2
let b = a + 3
let c =
{
apple: a,
banana: b
}
let d = c.apple * 4

Наведите курсор на a, b, c, d и обратите внимание, как TypeScript выводит
типы всех переменных: a является number, b и d также являются number,
а c — это объект определенной формы (рис. 2.3).

Рис. 2.3. TypeScript выводит типы за вас

Поэкспериментируйте с кодом и посмотрите, сможете ли вы:
‰‰Спровоцировать TypeScript использовать красную волнистую линию,

указывающую на ошибку (мы зовем это выдачей TypeError (ошибки
типа)).
‰‰Прочитать

TypeError и понять, что она значит.

‰‰Исправить

TypeError, то есть убрать красную линию.

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

ГЛАВА 3

Подробно о типах
В предыдущей главе мы говорили о системах типов, но не дали определения типам.

ТИП
Набор значений и применимых к ним операций.

Например:
‰‰Тип boolean

представляет набор всех логических значений (их два:
true и false) и операций, применимых к ним (вроде ||, && и !).

‰‰Тип number

представляет набор всех чисел и допустимых для них операций (вроде +, -, *, /, %, ||, && и ?), включая методы, которые можно
для них вызывать, такие как .toFixed, .toPrecision, .toString.

‰‰Тип string

представляет набор всех строк и производимых с ними
операций (вроде +, || и &&), включая методы, которые можно для них
вызывать, такие как .concat, .toUpperCase.

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

34  

Глава 3. Подробно о типах

Рис. 3.1. Иерархия типов в TypeScript

О типах
Для обсуждения типов воспользуемся общепринятой у программистов
терминологией.
Допустим, функция получает значение и возвращает его умноженным
на само себя:
function squareOf(n) {
return n * n
}
squareOf(2)
// вычисляется как 4
squareOf('z') // вычисляется как NaN

Ясно, что эта функция работает только с числами: если передать в squareOf
не число, то результат будет ошибочным. Итак, в этой ситуации мы прибегаем к аннотированию типа параметра:
function squareOf(n: number) {
return n * n
}
squareOf(2)
// вычисляется как 4
squareOf('z') // Ошибка TS2345: Аргумент типа '"z"' не может быть
// присвоен параметру типа 'number'.

Типы от а до я   35

Теперь, если мы вызовем squareOf со значением, отличным от числового,
TypeScript будет иметь основание для указания ошибки. Это тривиальный
пример (подробнее поговорим о функциях в следующей главе), но его
достаточно для демонстрации пары ключевых концепций обсуждения
типов в TypeScript. О последнем примере мы можем сказать следующее:
1. Параметр n, принадлежащий squareOf, ограничен типом number.
2. Тип значения 2 может быть присвоен (совместим с) number.
Без аннотации типа squareOf не ограничен своим параметром и вы можете
передать ему аргумент любого типа. Как только вы введете ограничение,
TypeScript проверит, во всех ли местах функция вызывается с подходящим
аргументом. В нашем примере 2 имеет тип number, который совместим
с аннотацией squareOf number, поэтому TypeScript принимает такой код.
Но 'z' является string, которая несовместима с number, и TypeScript
указывает на ошибку.
Вы также можете интерпретировать это в выражениях пределов: если
сообщить TypeScript, что верхний предел n — это number, любое значение,
передаваемое squareOf, не будет превышать number. В противном случае
значение не сможет быть присвоенным n.
Более формальное определение совместимости пределов и ограничений
вы найдете в главе 6. Сейчас важно понять, что перед вами язык, обсуждаемый в контексте использования некоего типа там, где требуется
конкретизировать тип.

Типы от а до я
Познакомимся с имеющимися в TypeScript типами и содержащимися
в них значениями и возможностями и затронем некоторые относящиеся
к ним базовые особенности языка, а именно псевдонимы типов, типы
объединения и типы пересечения.

any
any выступает в роли крестного отца всех типов. За соответствующую пла-

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

36  

Глава 3. Подробно о типах

вится типом по умолчанию там, где вы (программист) и TypeScript (модуль проверки) не можете точно определить тип элемента. Можно охарактеризовать его как тип крайнего случая.
Почему же его стоит избегать? Вспомните, чем является тип? Правильно.
Это набор значений и доступных для них действий. any же представляет
собой набор всех возможных значений, и вы можете делать с ним все что
угодно: прибавлять к нему, умножать на него, вызывать для него .pizza()
и т. д.
Значение, имеющее тип any, не дает возможности модулю проверки творить свое волшебство. Когда вы разрешаете any в коде, вы переходите
в режим «слепого полета».
В тех редких случаях, когда действительно необходимо его использовать,
делайте это так:
let a: any = 666
let b: any = ['danger']
let c = a + b

// any
// any
// any

Обратите внимание, что третий тип должен вызвать сообщение об
ошибке (ведь вы пытаетесь сложить число и массив), но этого не происходит, потому что вы сообщили TypeScript, что складываете два any.
Необходимо использовать any явно. Когда TypeScript выводит тип некоего значения как any (например, вы забыли аннотировать параметр
функции или импортировали нетипизированный JavaScript-модуль), то
в процессе компиляции он выбросит исключение и подчеркнет ошибку
красным. Используя же явную аннотацию a и b типом any (:any), вы избежите исключения, потому что таким образом дадите TypeScript понять
осознанность этого действия.

TSC-ФЛАГ NOIMPLICITANY
По умолчанию TypeScript не жалуется на значения, для которых он
вывел тип any (неявные any). Чтобы активировать функцию защиты
от неявных any, нужно добавить флаг noImplicitAny в tsconfig.json.
становится активна при включении режима strict
в tsconfig.json (см. подраздел «tsconfig.json» на с. 28).
noImplicitAny

Типы от а до я   37

unknown
Если any — это крестный отец, то unknown — это Киану Ривз в роли Джонни Юты из фильма «На гребне волны» — самоуверенный агент ФБР
под прикрытием, который втирается в круг плохих парней, но хранит
верность закону. В случае, когда у вас есть значение, чей тип вы узнаете
позднее, вместо any примените unknown. Он представляет любое значение,
но чтобы использовать это значение, TypeScript потребует уточнить его
тип (см. подраздел «Уточнение» на с. 160).
Какие же операции поддерживает unknown? Вы можете сравнивать значения unknown (==, ===, ||, && и ?), отрицать их (!) и уточнять (как и любой
другой тип через JavaScript-операторы typeof и instanceof). Применяется
же он следующим образом:
let a: unknown = 30
let b = a === 123
let c = a + 10
if (typeof a === 'number')
let d = a + 10
}

// unknown
// boolean
// Ошибка TS2571: объект имеет тип 'unknown'.
{
// number

Этот пример дает общее представление об использовании unknown.
1. TypeScript никогда не выводит unknown — этот тип нужно явно аннотировать (a)1.
2. Можно сравнивать значения со значениями типа unknown (b).
3. Нельзя производить действия на основе предположения, что значение
unknown имеет конкретный тип (c). Сначала нужно показать TypeScript
наличие этого типа (d).

boolean
Тип boolean (логический) имеет всего два значения: true и false. Такие
типы можно сравнивать (==, ===, ||, && и ?) и отрицать (!). Используются
они так:
1

Почти никогда. Если unknown является частью типа объединения, результатом объединения будет unknown (см. подраздел «Типы объединения и пересечения» на с. 50).

38  

Глава 3. Подробно о типах

let a = true
var b = false
const c = true
let d: boolean = true
let e: true = true
let f: true = false

//
//
//
//
//
//
//

boolean
boolean
true
boolean
true
Ошибка TS2322: тип 'false' не может быть
присвоен типу 'true'.

Согласно этому примеру, чтобы сообщить TypeScript, что некий элемент
имеет тип boolean, можно сделать следующее:
1. Позволить TypeScript вывести тип boolean для значения (a и b).
2. Позволить TypeScript вывести конкретное значение boolean (c).
3. Явно сообщить TypeScript, что значение является boolean (d).
4. Явно сообщить TypeScript, что значение имеет конкретное значение
boolean (e и f).
В большинстве случаев используются первые два способа, очень редко
(если повышается типобезопасность) — четвертый (увидите на примерах)
и практически никогда — третий.
Второй и четвертый случаи особенно интересны, поскольку хоть они
и делают нечто естественное, поддерживаются они на удивление малым
количеством языков и могут оказаться для вас незнакомыми. В том примере я обратился к TypeScript: «Видишь эту переменную e? Она не просто
какой-то там boolean, а совершенно конкретный Boolean — true». Использовав значение в качестве типа, я существенно ограничил возможные
для e и f значения boolean, определив для каждого конкретное, — создал
литералы типов.

ЛИТЕРАЛ ТИПА
Тип, представляющий только одно значение
и ничто другое.

В четвертом случае я явно аннотировал переменные литералами типов,
а во втором TypeScript вывел литералы типов за меня, потому что я использовал const вместо let или var. TypeScript знает, что значение примитива, присвоенного с const, не меняется, и выводит для этой переменной

Типы от а до я   39

максимально узкий тип. Именно поэтому во втором случае TypeScript
вывел тип c как true вместо boolean. Вывод типов для let и const по­
дробно рассмотрен в подразделе «Расширение типов» на с. 155.
Мы еще будем возвращаться к литералам типов. Они являются мощным
инструментом типобезопасности и делают TypeScript уникальным среди
других языков программирования, позволяя вам подтрунивать над друзьями, использующими Java.

number
Тип number представляет набор чисел: целочисленные, с плавающей запятой, положительные, отрицательные, Infinity (бесконечность), NaN
и т. д. Для чисел доступно достаточно много действий: сложение (+), вычитание (-), деление по модулю (%) и сравнение (<). Рассмотрим примеры:
let a = 1234
var b = Infinity * 0.10
const c = 5678
let d = a < b
let e: number = 100
let f: 26.218 = 26.218
let g: 26.218 = 10

//
//
//
//
//
//
//
//

number
number
5678
boolean
number
26.218
Ошибка TS2322: тип '10' не может быть
присвоен типу '26.218'.

Для типизации с помощь number можно сделать следующее:
1. Позволить TypeScript вывести тип значения как number (a и b).
2. Использовать const, чтобы TypeScript вывел тип переменной как конкретное число (number) (c)1.
3. Явно сообщить TypeScript, что значение имеет тип number (e).
4. Явно сообщить, что значение имеет конкретный тип number (f и g).
Так же как и с boolean, лучше доверить вывод типа TypeScript (первый вариант). Иногда при утонченном описании программы потребуется ограничить
тип конкретным значением (второй и четвертый варианты). На практике
нет причин явно типизировать значение как number (третий вариант).
1

На момент написания книги применение NaN, Infinity или -Infinity в качестве
литералов типов не допускается.

40  

Глава 3. Подробно о типах

Работая с длинными числами, используйте десятичный разделитель
как в значениях, так и в типах для их удобного чтения:
let oneMillion = 1_000_000 // Эквивалент 1000000
let twoMillion: 2_000_000 = 2_000_000

bigint
Тип bigint является новичком в JavaScript и TypeScript. Он позволяет
работать с крупными целочисленными значениями без риска столкнуться
с ошибками округления. В то время как number может представлять целые
числа только до 253, bigint способен представить и бˆольшие значения.
Этот тип является набором всех BigInts и поддерживает такие действия,
как сложение (+), вычитание (-), умножение (*), деление (/) и сравнение (<). Используется он так:
let a
const
var c
let d
let e

=
b
=
=
=

1234n
= 5678n
a + b
a < 1235
88.5n

let f: bigint = 100n
let g: 100n = 100n
let h: bigint = 100

//
//
//
//
//
//
//
//
//
//

bigint
5678n
bigint
boolean
Ошибка TS1353: литерал bigint должен быть
целым числом.
bigint
100n
Ошибка TS2322: тип '100' не может быть
присвоен типу 'bigint'.

Аналогично boolean и number есть четыре варианта объявления bigint.
Старайтесь позволять TypeScript делать их вывод за вас.
На момент написания книги еще не все движки JavaScript поддерживают bigint по умолчанию. Если ваше приложение зависит от него,
то стоит убедиться в его поддержке на целевой платформе.

string
Тип string является набором всех строк и доступных для них операций
вроде конкатенации (+), среза (.slice) и т. д. Вот несколько примеров:

Типы от а до я   41

let a = 'hello'
var b = 'billy'
const c = '!'
let d = a + ' ' + b + c
let e: string = 'zoom'
let g: 'john' = 'zoe'

//
//
//
//
//
//
//

string
string
'!'
string
string let f: 'john' = 'john' // 'john'
Ошибка TS2322: тип "zoe" не может быть
присвоен типу "john".

Как и в случае с boolean и number, тип string можно объявить одним из
четырех способов, и лучше, если это сделает сам TypeScript.

symbol
Тип symbol появился с одной из последних ревизий JavaScript (ES2015).
На практике символы встречаются нечасто. Они используются в качестве
альтернативы строчных ключей в объектах и картах там, где вы хотите
быть абсолютно уверены, что пользователи применяют верный, хорошо
известный ключ и по случайности не установят другой. Подумайте, устанавливая итератор по умолчанию для объекта (Symbol.Iterator) или переназначая ключ в среде выполнения, не является ли объект экземпляром
чего-либо (Symbol.Instance). Символы обозначаются типом symbol и не
предполагают много действий:
let
let
var
let

a = Symbol('a')
b: symbol = Symbol('b')
c = a === b
d = a + 'x'

//
//
//
//
//

symbol
symbol
boolean
Ошибка TS2469: оператор '+' не может
быть применен к типу 'symbol'.

Задача Symbol('a') в JavaScript заключается в создании нового symbol с заданным именем. Этот symbol уникален и не будет равен (при сравнении
посредством == или ===) никакому другому symbol (даже если создать
второй одноименный symbol). Аналогично тому, как значение 27 приобретает тип number при объявлении с помощью let и становится конкретным
числом при объявлении с const, типы символов выводятся как symbol, но
также могут быть объявлены явно как unique symbol:
const e = Symbol('e')
const f: unique symbol = Symbol('f')
let g: unique symbol = Symbol('f')

//
//
//
//
//

typeof e
typeof f
Ошибка TS1332: переменная,
имеющая тип 'unique symbol',
должна быть 'const'.

42  

Глава 3. Подробно о типах

let h = e === e
let i = e === f

//
//
//
//

boolean
Ошибка TS2367: это условие всегда будет
возвращать 'false', поскольку типы 'unique
symbol' и 'unique symbol' не имеют сходства.

Этот пример демонстрирует несколько особенностей создания уникального символа:
1. Когда вы объявляете новый symbol и присваиваете его переменной
const (не var или let), TypeScript выводит ее тип как unique symbol.
В редакторе она будет отображаться не как unique symbol, а как typeof
имяПеременной.
2. Вы можете явно аннотировать тип const-переменной как unique symbol.
3. unique symbol всегда равен самому себе.
4. TypeScript при компиляции знает, что unique symbol никогда не будет
равен никакому другому unique symbol.
Воспринимайте unique symbol так, как и другие типы литералов вроде 1,
true или "literal". Они являются способом создать тип, представляющий
конкретное значение symbol.

Объекты
Тип object в TypeScript определяет форму объекта. Примечательно, что
он не отражает разницу между простыми объектами (вроде созданных
с помощью {}) и более сложными (созданными с помощью new). Так
и было задумано: JavaScript преимущественно структурно типизирован,
поэтому TypeScript избегает номинальной типизации.

СТРУКТУРНАЯ ТИПИЗАЦИЯ
Стиль программирования, в котором вас интересуют
только конкретные свойства объекта, а не его имя (номинальная типизация). В некоторых языках это называют
утиной типизацией (или «не судите книгу по обложке»).

В TypeScript есть несколько способов использования типов для описания
объектов. Первый заключается в объявлении значения в качестве object:

Типы от а до я   43

let a: object = {
b: 'x'
}

Что произойдет, когда вы обратитесь к b?
a.b

// Ошибка TS2339: свойство 'b' не существует в типе 'object'.

Но какой смысл присваивать чему-либо тип object, если потом ничего
нельзя делать?
На самом деле смысл есть. В малой степени object является any. Этот
тип сообщает вам о значении, которому он присвоен, только то, что оно
является объектом JavaScript (и оно не null).

ВЫВОД ТИПОВ ПРИ ОБЪЯВЛЕНИИ ОБЪЕКТОВ С CONST
Что произойдет, если для объявления объекта использовать const?
const a: {b: number} = {
b: 12
}
// По-прежнему {b: number}

Вас может удивить, что TypeScript вывел тип b как number, а не литерал 12. Ранее
мы выяснили, что при объявлении типов number или string выбор const или
let влияет на вывод типов.
В отличие от примитивных типов, рассмотренных прежде (boolean, number,
bigint, string и symbol), объект, объявленный с const, не подскажет TypeScript
вывести более узкий тип. Дело в том, что JavaScript-объекты изменяемы
и TypeScript знает, что их поля можно обновить после создания.
В подразделе «Расширение типов» на с. 155 мы более подробно рассмотрим эту
тему, а также то, как добиться вывода более узкого типа.

А что, если мы оставим явные аннотации и позволим TypeScript делать
свою работу?
let a = {
b: 'x'
}
a.b

// {b: string}
// string

44  

Глава 3. Подробно о типах

let b = {
c: {
d: 'f'
}
}

// {c: {d: string}}

Вуаля! Вы только что открыли второй способ типизации объекта:
синтаксис объектного литерала. Вы можете либо позволить TypeScript
вывести форму объекта, либо описать ее явно, используя фигурные
скобки {}:
let a: {b: number} = {
b: 12
}
// {b: number}

Синтаксис объектного литерала сообщает: «Здесь находится элемент
такой формы». Этот элемент может быть либо объектным литералом,
либо классом:
let c: {
firstName: string
lastName: string
} = {
firstName: 'john',
lastName: 'barrowman'
}
class Person {
constructor(
public firstName: string,

// public является сокращением
// this.firstName = firstName

public lastName: string
) {}
}
c = new Person('matt', 'smith')

// OK

{firstName: string, lastName: string} описывает форму объекта. Объ-

ектный литерал и экземпляр класса из последнего примера соответствуют
этой форме, поэтому TypeScript позволяет присвоить Person литералу c.
Вот что произойдет, если добавить дополнительные свойства или упустить необходимые:

Типы от а до я   45

let a: {b: number}
a = {}

a = {
b: 1,
c: 2
}

// Ошибка TS2741: свойство 'b' отсутствует в типе '{}',
// но необходимо в типе '{b: number}'.

//
//
//
//

Ошибка TS2322: тип '{b: number; c: number}' не может
быть присвоен типу '{b: number}'. Объектный литерал
может определять только известные свойства,
а 'c' не существует в типе '{b: number}'.

ПРИСВОЕНИЕ
Это первый пример, когда сначала мы объявляем переменную (a), а затем ее
инициализируем со значениями ({} и {b:1, c:2}). Такой паттерн JavaScript
поддерживается в TypeScript.
Когда вы объявляете переменную в одном месте, а инициализируете ее позже,
TypeScript убеждается, что этой переменной точно присвоено значение на
момент ее использования:
let i: number
let j = i * 3

// Ошибка TS2454: переменная 'i' используется
// до присвоения ей значения.

TypeScript проследит за этим, даже если вы не сделаете явные аннотации
типов:
let i
let j = i * 3

// Ошибка TS2532: объект, вероятно,
// 'undefined'.

По умолчанию TypeScript достаточно строг относительно свойств объекта. Если вы сообщаете, что объект должен иметь свойство с именем
b типа number, то TypeScript будет ожидать b и только b. Если b будет
отсутствовать или появятся дополнительные свойства, то он будет
ругаться.
Можно ли сообщить TypeScript, что нечто является опциональным или
возможно появление других запланированных свойств? Конечно:

46  

Глава 3. Подробно о типах

let a: {
b: number ❶
c?: string ❷
[key: number]: boolean ❸
}

❶ a имеет свойство b, являющееся number.

❷ a может иметь свойство c, являющееся string. Если с задано, то оно
может быть undefined.

❸ a может иметь любое количество численных свойств, являющихся boolean.

Рассмотрим, какие типы объектов можно присвоить a:
a
a
a
a
a
a

=
=
=
=
=
=

{b: 1}
{b: 1, c: undefined}
{b: 1, c: 'd'}
{b: 1, 10: true}
{b: 1, 10: true, 20: false}
{10: true}
// Ошибка TS2741: свойство 'b' упущено
// в типе'{10: true}'.
a = {b: 1, 33: 'red'}
// Ошибка TS2741: тип 'string' не может
// быть присвоен типу 'boolean'.

При объявлении типа object можно использовать как модификатор
опциональности (?), так и модификатор readonly, который не позволит
изменять поле после присвоения ему первого значения (своего рода const
для свойств объекта):
let user: {
readonly firstName: string
} = {
firstName: 'abby'
}
user.firstName
user.firstName =
'abbey with an e'

// string
// Ошибка TS2540: невозможно присвоить
// к 'firstName', т.к. это свойство только
// для чтения.

Каждый тип, за исключением null и undefined, может быть присвоен типу
пустого объекта ({}), что усложняет его использование. Старайтесь избегать типов пустых объектов:

Типы от а до я   47

let danger: {}
danger = {}
danger = {x: 1}
danger = []
danger = 2

СИГНАТУРЫ ИНДЕКСОВ
Синтаксис [key: T}: U называется сигнатурой индекса. С ее помощью вы сообщаете компилятору, что данный объект может содержать больше ключей.
Читать его следует так: «Для этого объекта все ключи типа T должны иметь
значения типа U». Сигнатуры индекса позволяют безопасно добавлять дополнительные ключи объекту, помимо объявленных ранее.
Но тип (T) ключа сигнатуры индекса должен быть совместим либо со string,
либо с number1.
В качестве имени ключа сигнатуры индекса можно использовать любое слово — не только key:
let airplaneSeatingAssignments: {
[seatNumber: string]: string
} = {
'34D': 'Boris Cherny',
'34E': 'Bill Gates'
}

И стоит упомянуть еще один способ типизации чего-либо в качестве объекта: Object. Это практически то же самое, что использование {}, и лучше
этого избегать2.
1

2

Объекты в JavaScript используют для ключей строки. Массивы же являются особым
видом объектов и используют в качестве ключей числа.
Есть одно небольшое техническое отличие: {} позволяет определять любые
желаемые типы для встроенных методов в прототипе Object вроде .toString
и .hasOwnProperty (подробно о прототипах можете узнать на MDN), в то время
как Object следит, чтобы объявляемые вами типы могли быть присвоены к типам
прототипа Object. Например, следующий код проходит проверку типов: let a:
{} = {toString() { return 3}}. Но, если вы измените аннотацию типа на Object,
то TypeScript укажет ошибку: let b: Object = {toString() { returns 3 }} —
«Тип 'number' не может быть присвоен типу 'string'».

48  

Глава 3. Подробно о типах

В общей сложности выходит четыре способа объявления объектов
в TypeScript:
1. Объявление объектного литерала (вроде {a: string}), называемого
также формой. Используйте ее, когда знаете, какие поля будет иметь
объект, или когда все его значения будут иметь один тип.
2. Объявление пустого объектного литерала ({}). Старайтесь его избегать.
3. Тип object. Используйте его, когда вам просто нужен объект и неважно, какие у него поля.
4. Тип Object. Старайтесь его избегать.
Используйте линтер для предупреждения появления второго и четвертого
вариантов, отмечайте их непригодность в обзорах кода, печатайте плакаты,
то есть используйте предпочтительный для вашей команды инструмент,
чтобы держать их подальше от базы кода.
Таблица 3.1 представляет удобную справку относительно способов 2–4
предыдущего списка.
Таблица 3.1. Является ли значение допустимым объектом
Value

{}

object

Object

{}

Да

Да

Да

['a']

Да

Да

Да

function () {}

Да

Да

Да

new String('a')

Да

Да

Да

new String('a')

Да

Да

Да

'a'

Да

Нет

Да

1

Да

Нет

Да

Symbol('a')

Да

Нет

Да

null

Нет

Нет

Нет

undefined

Нет

Нет

Нет

Типы от а до я   49

Перерыв: псевдонимы, объединения и пересечения типов
Вы быстро осваиваете TypeScript: изучили несколько видов типов и принципов их работы, ознакомились с концепциями системы типов и типобез­
опасности. Пришло время погружаться глубже.
Как вы уже знаете, если есть значение, то в зависимости от его типа с ним
можно производить определенные операции. Например, использовать +
для сложения двух чисел или .toUpperCase для перевода строки в верхний
регистр.
С самим типом тоже можно производить операции.

Псевдонимы типов
Подобно тому как вы используете декларации (let, const и var) для объявления переменной, выступающей псевдонимом значения, вы также можете объявлять псевдоним типа, указывающий на тип. Выглядит это так:
type Age = number
type Person = {
name: string
age: Age
}
Age может быть только number. Это также помогает облегчить понимание
определения формы Person. TypeScript не делает вывод псевдонимов,

поэтому их нужно объявлять явно:
let age: Age = 55
let driver: Person = {
name: 'James May'
age: age
}

Поскольку Age является псевдонимом number, значит, он совместим с number
и можно переписать код так:
let age = 55
let driver: Person = {

50  

Глава 3. Подробно о типах

name: 'James May'
age: age
}

Псевдоним типа всегда можно заменить типом, на который он указывает.
Как и в случае с объявлением переменных (let, const и var), объявить
тип дважды нельзя:
type Color = 'red'
type Color = 'blue'

// Ошибка TS2300: повтор идентификатора 'Color'.

Так же как для let и const, диапазон псевдонимов типов ограничен одним
блоком. Каждый блок и каждая функция имеют свой диапазон, и внутренние объявления псевдонимов типов перекрывают внешние:
type Color = 'red'
let x = Math.random() < .5
if (x) {
type Color = 'blue' // Здесь перекрывается Color, объявленный выше.
let b: Color = 'blue'
} else {
let c: Color = 'red'
}

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

Типы объединения и пересечения
Объединением A и B будет их сумма (все, что есть в A, или в B, или в обоих), пересечение же — это то, что у них есть общего (все, что есть и в A,

1

Код не должен быть повторяющимся. Концепция DRY (Don’t Repeat Yourself) была
представлена Эндрю Хантом и Дэвидом Томасом в книге «Программист-прагматик:
путь от подмастерья к мастеру» (СПб.: Питер, 2007).

Типы от а до я   51

и в B). Представим это как наборы (рис. 3.2). Слева изображено объединение (сумма) двух наборов, а справа — их пересечение (произведение).
Объединение

Пересечение

Рис. 3.2. Объединение (|) и пересечение (&)

TypeScript предоставляет специальные операторы типов для описания
объединений и пересечений: | для объединения и & для пересечения.
Поскольку типы во многом схожи с наборами, мы можем воспринимать
их как наборы:
type
type
type
type

Cat = {name: string, purrs: boolean}
Dog = {name: string, barks: boolean, wags: boolean}
CatOrDogOrBoth = Cat | Dog
CatAndDog = Cat & Dog

Что известно о CatOrDogOrBoth? Мы видим только свойство name, явля­
ющееся string. А что можно присвоить CatOrDogOrBoth? Пожалуй, Cat,
Dog или и то и другое.
// Cat
let a: CatOrDogOrBoth = {
name: 'Bonkers',
purrs: true
}
// Dog
a = {
name: 'Domino',
barks: true,
wags: true
}
// И то и другое
a = {
name: 'Donkers',
barks: true,

52  

Глава 3. Подробно о типах

purrs: true,
wags: true
}

Повторный перебор вполне уместен, поскольку значение с типом объединения (|) может относиться не к одному из членов объединения,
а к обоим членам сразу1.
С другой стороны, что вы знаете о CatAndDog? Ведь гибридный суперпитомец умеет мурлыкать, лаять и вилять хвостом:
let b: CatAndDog = {
name: 'Domino',
barks: true,
purrs: true,
wags: true
}

Объединения, как правило, встречаются чаще, чем пересечения. Рассмот­
рим следующую функцию:
function trueOrNull(isTrue: boolean) {
if (isTrue) {
return 'true'
}
return null
}

Каков будет тип возвращаемого ей значения? Это может быть string или
null. То есть можно выразить возвращаемый тип так:
type Returns = string | null

А как насчет следующего примера?
function(a: string, b: number) {
return a || b
}

Если a окажется верно, то возвращаемый тип будет string, в противном
случае он будет number. Иначе говоря, он string | number.
1

Ознакомьтесь с подразделом «Типы размеченного объединения» на с. 162, чтобы
понять, каким образом можно подсказать TypeScript, что объединение не является
связанным и его значение должно иметь один из двух типов, но не оба одновременно.

Типы от а до я   53

Последний естественный случай появления объединений — это массивы (разнородный их вид).

Массивы
Как и в JavaScript, в TypeScript массивы являются особыми объектами,
поддерживающими конкатенацию, передачу, поиск и срезы. Вот пример:
let a = [1, 2, 3]
var b = ['a', 'b']
let c: string[] = ['a']
let d = [1, 'a']
const e = [2, 'b']
let f = ['red']
f.push('blue')
f.push(true)

//
//
//
//
//

number[]
string[]
string[]
(string | number)[]
(string | number)[]

// Ошибка TS2345: аргумент типа
// 'true' не может быть присвоен
// параметру типа 'string'.

let g = []
g.push(1)
g.push('red')

// any[]
// number[]
// (string | number)[]

let h: number[] = []
h.push(1)
h.push('red')

//
//
//
//
//

number[]
number[]
Ошибка TS2345: аргумент типа
'"red"' не может быть присвоен
параметру типа 'number'.

TypeScript поддерживает два варианта синтаксиса для массивов: T[]
и Array<T>. Они идентичны по значению и действию. В этой книге
применяется вариант T[], поскольку он более сжат, но вам стоит самостоятельно решить, какой из них подходит больше для вашего кода.

По мере ознакомления с примерами обратите внимание, что все, кроме
с и h, типизировано неявно и есть правила относительно того, что можно,
а что нельзя помещать в массив.

54  

Глава 3. Подробно о типах

Главное правило гласит, что массивы должны сохранять однородность —
не стоит смешивать яблоки с апельсинами и числами в одном массиве, чтобы не пришлось лишний раз подтверждать безопасность своих действий.
Понять, почему все гораздо проще, если массив однороден, можно, взглянув на пример f. Я инициализировал массив со строкой 'red' (на момент
объявления массив содержал только строки, и TypeScript сделал вывод,
что это массив строк). Затем я передал в него 'blue', являющееся строкой,
и TypeScript позволил это. Затем я попробовал передать в массив true,
и попытка провалилась.
С другой стороны, когда я инициализировал d, то определил в нем number
и string, и TypeScript сделал вывод, что массив имеет тип number | string.
Поскольку каждый элемент может быть либо числом, либо строкой, перед
его использованием нужно проверять, чем он является. Например, вы
хотите сделать отображение этого массива, преобразовав каждую букву
в верхний регистр и утроив каждое число:
let d = [1, 'a']
d.map(_ => {
if (typeof _ === 'number') {
return _ * 3
}
return _.toUpperCase()
})

Прежде чем использовать любой из элементов, нужно запросить его тип
посредством typeof.
Как и с объектами, создание массива с const не побудит TypeScript делать
более узкий вывод типа. Вот почему он вывел, что и d, и e имеют тип
number | string.
Вариант с g особенный. Когда вы инициализируете пустой массив, TypeScript
не знает, какой тип будут иметь его элементы, и присваивает ему тип any.
По мере добавления в массив новых значений TypeScript постепенно
определяет его тип в соответствии с ними. Как только массив выйдет за
определенный диапазон (например, если вы объявили его в функции,
а затем вернули), тогда TypeScript присвоит ему последний тип, который
не может быть расширен далее:

Типы от а до я   55

function buildArray() {
let a = []
a.push(1)
a.push('x')
return a
}
let myArray = buildArray()
myArray.push(true)

// any[]
// number[]
// (string | number)[]

//
//
//
//

(string | number)[]
Ошибка 2345: аргумент типа 'true'
не может быть присвоен параметру
типа 'string | number'.

До тех пор пока использование any проходит, можете не переживать.

Кортежи
Кортежи являются подтипами array . Они позволяют типизировать
массивы фиксированной длины, в которых значения каждого индекса
имеют конкретные известные типы. В отличие от большинства других
типов, кортежи в момент их объявления должны типизироваться явно.
Это вызвано тем, что в JavaScript для кортежей и массивов используется одинаковый синтаксис (квадратные скобки), а в TypeScript уже есть
правила вывода типов из квадратных скобок.
let a: [number] = [1]
// Кортеж [имя, фамилия, год рождения]
let b: [string, string, number] = ['malcolm', 'gladwell', 1963]
b = ['queen', 'elizabeth', 'ii', 1926] // Ошибка TS2322: тип
// 'string' не может быть присвоен типу 'number'.

Кортежи также поддерживают опциональные элементы. Как и для типов
object, опциональность обозначается знаком ?:
// Массив железнодорожных тарифов, который может меняться
// в зависимости от направления
let trainFares: [number, number?][] = [
[3.75],
[8.25, 7.70],
[10.50]
]

56  

Глава 3. Подробно о типах

// Эквивалент:
let moreTrainFares: ([number] | [number, number])[] = [
// ...
]

Кортежи также поддерживают оставшиеся элементы, которые вы можете
использовать для типизации кортежей минимальной длины:
// Список строк с как минимум одним элементом
let friends: [string, ...string[]] = ['Sara', 'Tali', 'Chloe', 'Claire']
// Разнородный список
let list: [number, boolean, ...string[]] = [1, false, 'a', 'b', 'c']

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

Массивы и кортежи только для чтения
Обычные массивы являются изменяемыми (можно добавлять в них (.push),
удалять из них или вставлять в них (.splice) и обновлять их на месте),
что, вероятно, и понадобится. Однако иногда нужен именно неизменяемый массив — тот, при обновлении которого формируется новый массив.
В TypeScript по умолчанию имеется тип массива readonly, который можно
использовать для создания неизменяемых массивов. Массивы только
для чтения похожи на обычные, но их нельзя обновить на месте. Чтобы
создать такой массив, используйте неизменяющие методы вроде .concat
и .slice вместо изменяющих — .push или .splice:
let as: readonly number[] = [1, 2, 3]
let bs: readonly number[] = as.concat(4)
let three = bs[2]
as[4] = 5

as.push(6)

//
//
//
//
//
//
//
//
//
//
//
//

readonly number[]
readonly number[]
number
Ошибка TS2542:
сигнатура индекса
в типе'readonly
number[]' допускает
только чтение.
Ошибка TS2339: свойство
'push' не существует
в типе 'readonly
number[]'.

Типы от а до я   57

Как и в случае с Array, в TypeScript есть пара более длинных форм для
объявления массивов и кортежей только для чтения:
type A = readonly string[]
type B = ReadonlyArray<string>
type C = Readonly<string[]>

// readonly string[]
// readonly string[]
// readonly string[]

type D = readonly [number, string]
type E = Readonly<[number, string]>

// readonly [number, string]
// readonly [number, string]

Какой именно синтаксис использовать — более краткий модификатор
readonly или более длинную форму Readonly или ReadonlyArray, — это дело
вкуса.
Обратите внимание, что массивы только для чтения могут облегчить
восприятие кода, но они подкреплены обычными массивами JavaScript,
поэтому даже для небольшого изменения массива необходимо сначала
копировать его оригинал, что может при неосторожности навредить производительности приложения. Подобные издержки незаметны в случаях
с небольшими массивами, но становятся весьма ощутимы при работе
с крупными.
Если вы собираетесь активно использовать неизменяемые массивы,
рассмотрите применение более эффективных реализаций наподобие
immutable Ли Байрона.

null, undefined, void и never
В JavaScript есть два значения для выражения отсутствия: null и undefined.
TypeScript поддерживает их оба и имеет для них типы. Есть предположения, как они называются? Да, null и undefined.
В TypeScript тип undefined может иметь только одно значение — undefined,
то же касается и типа null, который описывает значение null.
JavaScript-программисты обычно используют их как взаимозаменяемые,
хотя между ними и есть тонкое семантическое различие, достойное упоминания: undefined означает, что нечто еще не было определено, а null
показывает отсутствие значения (как если бы вы пытались вычислить
значение, но сталкивались при этом с ошибкой). Это всего лишь услов-

58  

Глава 3. Подробно о типах

ности, и TypeScript вас не принуждает к их соблюдению, однако будет
полезным понимать разницу.
Типы void и never проводят различие между несуществующими вещами:
void — это возвращаемый тип функции, которая не возвращает ничего
явно (например, console.log), а never — это тип функции, которая никогда
ничего не возвращает (выбрасывает исключение или выполняется бесконечно):
// (a) Функция, возвращающая число или null
function a(x: number) {
if (x < 10) {
return x
}
return null
}
// (b) Функция, возвращающая undefined
function b() {
return undefined
}
// (c) Функция, возвращающая void
function c() {
let a = 2 + 2
let b = a * a
}
// (d) Функция, возвращающая never
function d() {
throw TypeError('I always error')
}
// (e) Другая функция, возвращающая never
function e() {
while (true)
{ doSomething()
}
}

(a) и (b) явно возвращают null и undefined соответственно. (c) возвращает
undefined, но не делает этого при явной инструкции return, поэтому мы

Типы от а до я   59

говорим, что она возвращает void. (d) выбрасывает исключение, а (e)
выполняется вечно — обе они никогда ничего не вернут, поэтому их возвращаемый тип — never.

СТРОГАЯ ПРОВЕРКА НА NULL
В более старых версиях TypeScript (или при опции strictNullChecks, установленной как false) null ведет себя необычно: он является подтипом всех
типов, кроме never. Это значит, что каждый тип может быть null и вы не
можете быть ни в чем уверены, пока не произведете проверку на null. Например, если кто-нибудь передает вашей функции переменную pizza и вы
хотите вызвать для нее метод .addAnchovies (добавить анчоус), то сначала вы
должны проверить, не является ли pizza null, и только затем добавлять в нее
рыбку. На практике утомительно делать это для каждой переменной, поэтому
о предварительной проверке часто забывают. В таких случаях, когда что-либо
действительно оказывается null, при выполнении вы получаете зловещее исключение нулевого указателя:
function addDeliciousFish(pizza: Pizza) {
return pizza.addAnchovies() // Не перехваченная ошибка типа:
}
// невозможно прочитать свойство
// 'addAnchovies', являющееся null
// TypeScript допускает подобное при strictNullChecks = false
addDeliciousFish(null)
null был назван «ошибкой на триллион долларов» (https://www.infoq.com/
presentations/Null-References-The-Billion-Dollar-Mistake-Tony-Hoare/)

тем, кто впервые
обозначил его в 1960 году. Проблема в том, что его невозможно выразить и проверить в системах типов большинства языков. Поэтому, когда программисты
пытаются сделать что-либо с переменной, которую они считают определенной,
но которая при выполнении оказывается null, код выбрасывает исключение
среды выполнения.
Почему? Откуда я знаю. Но языки находят способы кодирования null в их
системах типов, и TypeScript является отличным примером того, как это делать
правильно. Если цель в обнаружении как можно большего количества багов
в процессе компиляции, прежде чем с ними столкнутся пользователи, тогда
возможность проверки на null просто необходима в системе типов.

60  

Глава 3. Подробно о типах

Если unknown — это супертип любого другого типа, то never — это подтип любых других типов, или низший тип (bottom type), который может
быть присвоен любому другому типу, и значение типа never может быть
использовано везде безопасно. Это все теория1, но пригодится в диалогах
о TypeScript с другими знатоками языков.
Таблица 3.2 подытоживает принципы использования типов отсутствия.
Таблица 3.2. Типы, означающие отсутствие чего-либо
Type

Meaning

null

Отсутствие значения

undefined

Переменная, которой не присвоено значение

void

Функция, не имеющая оператора return

never

Функция, никогда ничего не возвращающая

Enum
Enum является способом перечисления возможных значений типа. Он

представляет собой неупорядоченную структуру данных, которая сопоставляет ключи и значения. Воспринимайте эти значения как объекты,
имеющие во время компиляции фиксированные ключи, что позволяет
TypeScript убедиться, что данный ключ будет существовать при обращении к значению.
Есть два типа enum: отображающий строки в строки и отображающий
строки в числа:
enum Language {
English,
Spanish,
Russian
}

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

Можно думать о низшем типе как о типе, который не имеет значений. Низший тип
соответствует математическому предложению, которое всегда ложно.

Типы от а до я   61

enum Language
English =
Spanish =
Russian =
}

{
0,
1,
2

Общепринято, что имена enum начинаются с верхнего регистра
и имеют форму единственного числа. Его ключи также начинаются
с верхнего регистра.

Чтобы извлечь значение из перечисления, обратитесь к нему через запись с точкой или скобкой — так же, как если бы вы получали значение
из обычного объекта:
let myFirstLanguage = Language.Russian
let mySecondLanguage = Language['English']

// Language
// Language

Можно разделить перечисление на несколько деклараций, и Type­Script
автоматически произведет их слияние (см. подраздел «Слияние декла­
ра­ций» на с. 122). Будьте осторожны при разделении перечисления,
TypeScript может вывести значения только для одной из деклараций,
поэтому лучше всего в таком случае явно присвоить значение каждому
члену перечисления:
enum Language {
English = 0,
Spanish = 1
}
enum Language {
Russian = 2
}

Можете использовать посчитанные значения, и вам не обязательно самстоятельно их определять:
enum Language {
English = 100,
Spanish = 200 + 300,
Russian // TypeScript выводит 501 (число, следующее за 500)
}

62  

Глава 3. Подробно о типах

Также вы можете использовать строчные значения для нумерации или
даже смешивать строчные и числовые значения:
enum Color {
Red = '#c10000',
Blue = '#007ac1',
Pink = 0xc10050,
White = 255
}
let red = Color.Red
let pink = Color.Pink

// Шестнадцатеричный литерал
// Десятичный литерал

// Color
// Color

TypeScript для удобства позволяет обращаться к перечислениям как по
значению, так и по ключу, но это легко может нарушить безопасность:
let a = Color.Red
let b = Color.Green
let c = Color[0]
let d = Color[6]

//
//
//
//
//

Color
Ошибка TS2339: свойство 'Green'
не существует в типе 'typeof Color'.
string
string (!!!)

У вас не должно быть возможности получить Color[6], но TypeScript вас
не останавливает. Мы можем попросить его предотвращать подобное
небезопасное обращение с помощью const enum. Давайте перепишем последнее перечисление Language:
const enum Language {
English,
Spanish,
Russian
}
// Обращение к верному ключу перечисления
let a = Language.English // Language
// Обращение к неверному ключу перечисления
let b = Language.Tagalog // Ошибка TS2339: свойство 'Tagalog'
// не существует в типе 'typeof Language'.
// Обращение к верному значению перечисления
let c = Language[0]
// Ошибка TS2476: обратиться к константному
// члену перечисления можно только с помощью
// строчного литерала.

Типы от а до я   63

// Обращение к неверному значению перечисления
let d = Language[6]
// Ошибка TS2476: обратиться к константному
// члену перечисления можно только
// с помощью строчного литерала.
const enum не позволяет производить обратный просмотр и поэтому во

многом ведет себя как обычный JavaScript-объект. Он также по умолчанию
не генерирует никакой JavaScript-код, а вместо этого подставляет значение члена перечисления везде, где он используется (например, TypeScript
заменит Language.Spanish на его значение 1 везде, где он встречается).

TSC-ФЛАГ: PRESERVECONSTENUMS
Подстановка, обусловленная const enum, может вызывать проблемы
безопасности при импорте const enum из чужого TypeScript-кода:
если автор перечисления обновит свой const enum после того, как вы
скомпилируете свой код, то ваши с ним версии могут указывать на
различные значения при выполнении, о чем TypeScript знать не будет.
Избегайте подстановок и используйте const enums только в контролируемых программах, которые вы не собираетесь публиковать в NPM
или делать доступными в качестве библиотек.
Чтобы активировать для const enums генерацию кода при выполнении, смените TSC-настройку preserveConstEnums на true в tsconfig.json:
{
"compilerOptions": {
"preserveConstEnums": true
}
}

Рассмотрите использование const enums:
const enum Flippable {
Burger,
Chair,
Cup,
Skateboard,
Table
}
function flip(f: Flippable) {

64  

Глава 3. Подробно о типах

return 'flipped it'
}
flip(Flippable.Chair)
flip(Flippable.Cup)
flip(12)

// 'flipped it'
// 'flipped it'
// 'flipped it' (!!!)

Все выглядит отлично — с Chairs и Cups происходит именно то, что вы
и ожидаете… пока вы не осознаете, что все числа также совместимы
с перечислениями. Такое поведение является неудачным последствием
правил совместимости в TypeScript. Чтобы это исправить, вам нужно быть
очень осторожными и использовать только перечисления со строчными
значениями:
const enum Flippable {
Burger = 'Burger',
Chair = 'Chair',
Cup = 'Cup',
Skateboard = 'Skateboard',
Table = 'Table'
}
function flip(f: Flippable) {
return 'flipped it'
}
flip(Flippable.Chair)
flip(Flippable.Cup)
flip(12)

flip('Hat')

//
//
//
//
//
//
//
//

'flipped it'
'flipped it'
Ошибка TS2345: аргумент типа '12'
не может быть присвоен параметру типа
'Flippable'.
Ошибка TS2345: аргумент типа '"Hat"'
не может быть присвоен параметру типа
'Flippable'.

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

Итоги   65

Из-за всех ловушек, которые затрудняют безопасное использование
перечислений, рекомендую держаться от них подальше.
Если же на их использовании настаивают ваши коллеги и их никак
нельзя переубедить, то обязательно внедрите специальные правила
TSLint, предупреждающие об использовании числовых значений
и неконстантных перечислений.

Итоги
TypeScript содержит множество встроенных типов. Вы можете разрешить ему выводить типы за вас на основе значений или же явно
типизировать значения. const позволит выводить более конкретные
типы, let и var — более общие. Большинство типов имеют основные
и более конкретные варианты, причем последние являются подтипами
первых (табл. 3.3).
Таблица 3.3. Типы и их более конкретные подтипы
Тип

Подтип

boolean

Boolean literal

bigint

BigInt literal

number

Number literal

string

String literal

symbol

unique symbol

object

Object literal

Array

Tuple

enum

const enum

66  

Глава 3. Подробно о типах

Упражнения к главе 3
1. Какой тип выведет TypeScript для каждого из этих значений?
a) let a = 1042
б) let b = 'apples and oranges'
в) const c = 'pineapples'
г) let d = [true, true, false]
д) let e = {type: 'ficus'}
е) let f = [1, false]
ж) const g = [3]
з) let h = null (выполните это в редакторе, если результат вас удивит, то перейдите к подразделу «Расширение типов» на с. 155).
2. Почему каждый из этих примеров выдает ошибку?
a)
let i: 3 = 3
i = 4

// Ошибка TS2322: тип '4' не может
// быть присвоен типу '3'.

б)
let j = [1, 2, 3]
j.push(4)
j.push('5')

// Ошибка TS2345: аргумент типа '5'
// не может быть присвоен параметру
// типа 'number'.

в)
let k: never = 4

// Ошибка TSTS2322: тип '4' не может
// быть присвоен типу 'never'.

г)
let l: unknown = 4
let m = l * 2

// Ошибка TS2571: объект имеет тип
// 'unknown'.

ГЛАВА 4

Функции
В предыдущей главе мы рассмотрели основы системы типов TypeScript:
примитивные типы, объекты, массивы, кортежи и перечисления. Мы также изучили основы вывода типов и принципы их совместимости. Теперь
можно переходить к главному номеру программы (а для функциональных
программистов — к смыслу жизни) — к функциям. В этой главе мы раскроем следующие темы.
‰‰Разные

способы объявления и вызова функций в TypeScript.

‰‰Перегрузка

сигнатуры.

‰‰Полиморфные

функции.

‰‰Полиморфные

псевдонимы типов.

Объявление и вызов функций
В JavaScript функции являются объектами первого уровня. Это означает,
что их можно использовать как объекты: присваивать переменным, передавать другим функциям, возвращать из функций, присваивать объектам
и прототипам, записывать в них свойства, считывать эти свойства и т. д.
TypeScript моделирует все эти возможности с помощью своей богатой
системы типов.
Так выглядит функция в TypeScript (пример вам знаком из предыдущей
главы):
function add(a: number, b: number) {
return a + b
}

Обычно параметры функции аннотируются явно (a и b в этом примере).
TypeScript выводит типы в теле функции, но не для параметров, за исклю-

68  

Глава 4. Функции

чением нескольких ситуаций, когда он ориентируется на контекст (подраздел «Контекстная типизация» на с. 82). Возвращаемый тип подлежит
выводу, но при желании его можно аннотировать явно:
function add(a: number, b: number): number {
return a + b
}

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

В последнем примере был использован синтаксис именованной функции,
но в JavaScript и TypeScript предлагается как минимум пять способов
объявления функции:
// Именованная функция
function greet(name: string) {
return 'hello ' + name
}
// Функциональное выражение
let greet2 = function(name: string) {
return 'hello ' + name
}
// Выражение стрелочной функции
let greet3 = (name: string) => {
return 'hello ' + name
}
// Сокращенное выражение стрелочной функции
let greet4 = (name: string) =>
'hello ' + name
// Конструктор функции
let greet5 = new Function('name', 'return "hello " + name')

Объявление и вызов функций   69

За исключением конструктора функции (который не рекомендуется
использовать1), эти виды синтаксиса поддерживаются TypeScript в типобезопасном режиме и следуют правилам, касающимся обязательных
аннотаций типов для параметров и опциональных аннотаций для возвращаемых типов.
Сверим часы.
yy Параметр — это часть данных, необходимых функции для запуска,
объявленная как часть декларации этой функции. Также может
называться формальным параметром.
yy Аргумент — это часть данных, предаваемая функции при ее вызове.
Также может называться актуальным параметром.

При вызове функции в TypeScript не нужно предоставлять дополнительную
информацию о типе — достаточно передать ей некий аргумент, и TypeScript
проверит совместимости этого аргумента с типами параметров функции:
add(1, 2)
greet('Crystal')

// вычисляется как 3
// выводится как 'hello Crystal'

Если вы забыли аргумент или передали аргумент неверного типа, TypeScript
поспешит на это указать:
add(1)
add(1, 'a')

// Ошибка TS2554: ожидается 2 аргумента, но получен 1.
// Ошибка TS2345: аргумент типа '"a"' не может быть
// присвоен параметру типа 'number'.

Предустановленные и опциональные параметры
Как и для типов object и кортежей, можно использовать ? для обозначения параметра в качестве опционального. При объявлении параметров
функции необходимые параметры должны идти перед опциональными:
1

Если вы введете последний пример в редактор, то увидите тип Function. Это вызываемый объект (если поместить за ним ()), который имеет все методы прототипа
из Function.prototype. Но его параметры и возвращаемый тип нетипизированы,
и вы можете вызвать такую функцию с любыми аргументами, причем TypeScript
даже не отреагирует на такие недопустимые действия.

70  

Глава 4. Функции

function log(message: string, userId?: string) {
let time = new Date().toLocaleTimeString()
console.log(time, message, userId || 'Not signed in')
}
log('Page loaded')
log('User signed in', 'da763be')

//
//
//
//

Логирует "12:38:31 PM
Page loaded Not signed in"
Логирует "12:38:31 PM
User signed in da763be"

Как и JavaScript, TypeScript позволяет снабдить опциональные параметры значениями по умолчанию. Семантически это схоже с назначением
параметра опциональным, поскольку вызывающий элемент не обязан
передавать такой параметр функции (разница в том, что параметр по
умолчанию можно не ставить в конец списка в отличие от опционального).
Например, можно переписать log так:
function log(message: string, userId = 'Not signed in') {
let time = new Date().toISOString()
console.log(time, message, userId)
}
log('User clicked on a button', 'da763be')
log('User signed out')

Заметьте, что когда мы присваиваем userId значение по умолчанию, то удаляем его опциональную аннотацию ?. Больше нет необходимости его типизировать. TypeScript вполне способен вывести тип параметра на основе его
значения по умолчанию, сохраняя при этом краткость и читабельность кода.
Конечно, вы также можете добавлять явные аннотации типов для параметров по умолчанию тем же способом, что и для обычных параметров:
type Context = {
appId?: string
userId?: string
}
function log(message: string, context: Context = {}) {
let time = new Date().toISOString()
console.log(time, message, context.userId)
}

Объявление и вызов функций   71

На практике вы будете использовать предустановленные параметры
чаще, чем опциональные.

Оставшиеся параметры
Если функция получает список аргументов, можно передать этот список
в виде массива:
function sum(numbers: number[]): number {
return numbers.reduce((total, n) => total + n, 0)
}
sum([1, 2,