Часто разработчики воспринимают типы как набор отличительных особенностей переменной. Это поверхностное видение мешает общему пониманию работы Typescript и поведению его анализатора. В результате приходится привыкать к разным приёмам типизации, вместо того, чтобы сделать для себя их очевидными. Прежде чем приступить к рассмотрению приёмов типизации, привьём себе немного более глубокий взгляд на типы и тогда требования языка Typescript к коду станут выглядеть более естественными. 1. Как типизировать данные2. Как понимать типы3. Как использовать типизацию4. Глубокая типизация5. Связанная типизация6. Изящная типизация Когда мы задаём тип для переменной, то мы сообщаем ей, что она может принять любое значение из множества, которое включает этот тип. К примеру, тип string включает почти бесконечное множество строк, а тип number включает огромное множество чисел и значения NaN, Infinity, -Infinity.  Во множестве значений типа boolean всего два значения (true и false), а в типе null — всего одно значение. И это не отменяет понятие множества. Особенно хорошо данный подход воспринимается, когда мы начинаем комбинировать типы. С помощью оператора объединения типов можно объединить типы и создать новый. Два множества значений исходных типов объединятся в одно множество. Создадим новый тип enum, который будет хранить числовые значения Тип Numeric содержит множество из трёх значений: 1, 2, 3. И это множество является подмножеством типа number. По сути любые числовые enum будут принадлежать множеству number. Соответственно строчные enum принадлежат типу string. Принадлежность подмножества множеству обозначается знаком ⊂. Numeric ⊂  number, number ⊂ number | string и т. п. Надеюсь, вас не смущает небольшое погружение в математику. Теорию множеств школьники проходят в третьем классе, раньше, чем учатся складывать столбиком. Всё это говорит в пользу простоты этой науки. На рисунках со множествами изображены диаграммы Эйлера — Венна. В последующих статьях принадлежность множеств значений к различных типам сыграет важную роль в определении подходов к типизации.  Теперь нам следует рассмотреть множества значений объектных типов и для этого сначала вспомним утиную типизацию. Если вашей программе понадобилась утка, то вы создаёте сущность, которая выглядит, плавает и крякает как утка. Прелесть такого подхода в том, что это необязательно должна быть утка. Интересную аналогию можно привести из жизни охотника (те, кто как и автор не охотился, могут представить компьютерную игру, где есть охота).  Представим, что добыча (утка а может быть даже олень) - это программа, а охотник - программист. Задача охотника приманить животное схожа с задачей программиста заставить программу следовать определенному поведению. Для этого охотник использует свисток, имитирующий голос животного, а также и нейтрализаторы человеческого запаха и одежду, чтобы слиться с природой и не спугнуть добычу. Утка или другое животное идут на зов, как и программа следует определённому поведению, потому что соблюдены особенности передаваемых ей переменных, то есть типы. Переменные объектного типа должны обязательно иметь те свойства, которые используются в коде. Typescript помогает за этим следить. Что вовсе не означает, что эти же переменные не могут иметь других свойств. На рисунке 2.4 чудовище использует утиную типизацию, чтобы корабль смог выполнить одну из своих функций - спасение утопающих. Так и разработчик передаёт в код переменные подходящего типа, заставляя работать его в нужном русле. К примеру, ref-атрибут может принимать функцию, в который будет передана ссылка на элемент. Разработчик даёт тот тип, который требуется, но использует поведение ref-атрибута по-своему. Код 2.1 передача в ref-атрибут колбэк-функции с целью отследить момент рендера элемента Рассмотрим следующий пример типизации. Код 2.2. Наследование типов Тип WithId, полученный через interface синтаксис, согласно утиной типизации включает почти бесконечное множество значений. Эти значения являются объектами с разнообразными свойствами, среди которых обязательно должно быть id со значением типа string. Тип UserData расширяет тип WithId с точки зрения количества свойств. Но с точки зрения значений тип UserData более узкий, чем WithId и является его подмножеством.  Тип WithId включает также огромное количество значений, но в отличии от UserData свойство name может быть другого типа, не равного string, или  вовсе отсутствовать. Возникает терминологический парадокс, который может сбивать с толку. Добавляя новые свойства в тип, мы расширяем его отличительные особенности, но сужаем количество значений, которые ему подходят. Наследование типов через type синтаксис происходит с помощью знака пересечения типов:  Код 2.3. Пересечение типов Когда мы ищем пересечение объектных типов, мы собираем их свойства вместе, что может показаться странным. Потому что когда мы ищем пересечения массивов (значений), то в результате получаем массив только с теми элементами, которые присутствуют в обоих исходных массивах. Почему это не работает с объектными типами? Тип с описанным свойством id является множеством объектов с разными свойствами, в том числе свойством name типа string. Тип с описанным свойством name, так же включает множество разных объектов, в том числе свойство id типа string. В итоге большей частью эти типы пересекаются. Каждое значение в пересечении должно относиться как к типу WithId, т.е. иметь свойство id, так и к типу UserData, т.е. иметь свойство name. И таких значений так же великое множество. Что если мы попробуем найти пересечения объектных типов с одинаковыми именами свойств, но разными типами: Код 2.4. Пересечение типов с одинаковыми свойствами Множества string и number не имеют пересечений (см. изобр. 2.1), значит мы не сможем подобрать такой объект, который удовлетворял бы особенностям обоих исходных типов. И тип WithId будет равен типу never. У типа never отсутствует множество значений. Иногда анализатор Typescript подсвечивает для вашей переменной тип never. Это означает, что в логике вашего кода он не может найти множества, которое соответствует переменной. Чья эта проблема? В следующих примерах рассмотрены некоторые ситуации, когда тип переменной определяется как never: Код 2.5. Примеры, когда переменная будет иметь тип never Во всех этих случаях анализатор видит, что инструкция в которой используется переменная, не будет выполнена. С точки зрения типизации это неверно спроектированный код и как следствие потенциально содержит ошибки. Типы any и unknown с точки зрения множеств противоположны never. Переменные этих типов могут принимать значения из множества, которое включает все остальные. Различаются эти типы тем, что на них по-разному реагирует анализатор. Рассмотрев базовые особенности синтаксиса Typescript и понимание типов как множеств мы перейдём к описанию способов, соглашений и предложений в типизации приложения. Следующая статья: TypeScript в React-приложениях. 3. Как использовать типизацию.