typescript что такое generic
Дженерики в TypeScript: разбираемся вместе
Всем привет! Команда TestMace публикует очередной перевод статьи из мира web-разработки. На этот раз для новичков! Приятного чтения.
Развеем пелену таинственности и недопонимания над синтаксисом и наконец подружимся с ним
Наверное, только матёрые разработчики Java или других строго типизированных языков не хлопают глазами, увидев дженерик в TypeScript. Его синтаксис коренным образом отличается от всего того, что мы привыкли видеть в JavaScript, поэтому так непросто сходу догадаться, что он вообще делает.
Я бы хотел показать вам, что на самом деле всё гораздо проще, чем кажется. Я докажу, что если вы способны реализовать на JavaScript функцию с аргументами, то вы сможете использовать дженерики без лишних усилий. Поехали!
Дженерики в TypeScript
В документации TypeScript приводится следующее определение: «дженерики — это возможность создавать компоненты, работающие не только с одним, а с несколькими типами данных».
Здорово! Значит, основная идея состоит в том, что дженерики позволяют нам создавать некие повторно используемые компоненты, работающие с различными типами передаваемых им данных. Но как это возможно? Вот что я думаю.
Дженерики и типы соотносятся друг с другом, как значения и аргументы функции. Это такой способ сообщить компонентам (функциям, классам или интерфейсам), какой тип необходимо использовать при их вызове так же, как во время вызова мы сообщаем функции, какие значения использовать в качестве аргументов.
Лучше всего разобрать это на примере дженерика тождественной функции. Тождественная функция — это функция, возвращающая значение переданного в неё аргумента. В JavaScript она будет выглядеть следующим образом:
Сделаем так, чтобы она работала с числами:
Отлично, мы добавили в определение тождественной функции тип, но хотелось бы, чтобы она была более гибкой и срабатывала для значений любого типа, а не только для чисел. Именно для этого и нужны дженерики. Они позволяют функции принимать значения любого типа данных на входе и, в зависимости от них, преобразовывать саму функцию.
Посмотрите на вызов функции. Теперь-то синтаксис дженериков не должен вас пугать. T и U — это просто имена переменных, которые вы назначаете сами. При вызове функции вместо них указываются типы, с которыми будет работать данная функция.
Альтернативная версия понимания концепции дженериков состоит в том, что они преобразуют функцию в зависимости от указанного типа данных. На анимации ниже показано, как меняется запись функции и возвращаемый результат при изменении типа.
Как можно видеть, функция принимает любой тип, что позволяет создавать повторно используемые компоненты различных типов, как и было обещано в документации.
Обратите особое внимание на второй вызов console.log на анимации выше — в него не передаётся тип. В этом случае TypeScript попытается вычислить тип по переданным данным.
Обобщённые классы и интерфейсы
Вам уже известно, что дженерики — это всего лишь способ передать типы в компонент. Только что вы видели, как они работают с функциями, и у меня хорошие новости: с классами и интерфейсами они работают точно таким же образом. В этом случае указание типов следует после имени интерфейса или класса.
Посмотрите на пример и попробуйте разобраться сами. Надеюсь, у вас получилось.
Если код сразу не понятен, попробуйте отследить значения type сверху вниз вплоть до вызовов функции. Порядок действий следующий:
Реальные случаи использования: выходим за рамки примитивных типов
Результатом выполнения данного кода будет:
Подведем итоги
Надеюсь, я помог вам разобраться с дженериками. Запомните, всё, что вам нужно сделать, — это всего лишь передать значение type в функцию 🙂
Если хотите ещё почитать про дженерики, я прикрепил далее пару ссылок.
TypeScript: Обобщённые типы (Generics)
Серия
В двух предыдущих статьях мы говорили о том, что из себя представляет TypeScript, зачем и когда он может вам понадобиться, а также о том, какие типы и структуры будут встречаться вам каждый день при написании кода на этом надмножестве языка JavaScript.
Также, мы вскользь задели тему обобщений, когда разговаривали о типе пересечения. Сегодня мы поговорим об обобщённых типах подробнее.
Первый взгляд на обобщения
Обобщённый тип (обобщение, дженерик) позволяет резервировать место для типа, который будет заменён на конкретный, переданный пользователем, при вызове функции или метода, а также при работе с классами.
Посмотрим на простейшую реализацию функции, описанной выше, если бы в TypeScript не было обобщённых типов и нам пришлось бы довольствоваться тем, что уже есть.
Всё как в описании: функция принимает аргумент любого типа и возвращает его же. Однако, это не совсем так. Внимательно присмотритесь к типам: в примере указано, что функция принимает аргумент какого-то типа и возвращает значение какого-то типа, но при этом они никак не связаны. Грубо говоря, сейчас мы можем передать аргумент типа number и получить значение типа string – это валидно, потому что any подразумевает под собой всё что угодно.
Сейчас функция представляет собой «чёрный ящик», в который с одной стороны что-то входит, а с другой стороны что-то выходит, возможно, похожее на то, что входило или нет – непонятно. Я бы сказал, что такую функцию можно называть «обезличивающей функцией» в контексте типов. При использовании any в качестве типа возвращаемого значения мы обезличиваем результат функции.
Теперь перепишем функцию так, чтобы каждый из нас знал, что тип переданного аргумента является и типом возвращаемого значения.
В мире C#, технически, можно утверждать, что identity – это открытый тип, а identity – замкнутый. При этом нужно понимать, что вы можете работать только с замкнутыми типами.
Попробуем вызвать эту функцию с аргументом типа number и поверхностно проследим за тем, как компилятор определяет, что вернёт функция.
Логический вывод обобщённых типов
Многих разработчиков смущает указание типа с помощью знаков «меньше» и «больше». Для упрощения создания, чтения и работы с кодом TypeScript имеет возможность логического вывода типов при вызове обобщённых типов. Это значит, что компилятор будет пытаться определить (логически вывести) тип, который автоматически будет использоваться при вызове обобщённого типа.
В отличие от C#, в TypeScript нет возможности написать две функции с одним именем, когда первая функция реализована для конкретного типа, а вторая – для обобщённого. Например, такое в TypeScript написать не получится:
Также стоит упомянуть, что компилятор выдаст ошибку, если он не сможет точно вывести тип для параметр-типа. Для того, чтобы разрешить сложившуюся ситуацию нужно явно указать типы.
Для чего существуют обобщённые типы
При написании кода вы всегда встаёте перед выбором, имеющим как минимум две ветви решения. Первый путь приводит вас к копированию существующего кода с нужным вам типом, что также приводит к «распуханию» кода и необходимости думать при рефакторинге. Второй путь – унификация, что подразумевает под собой параметрический полиморфизм, в основе которого лежит использование одного и того же кода, но с разными типами. Получается, что обобщённые типы нужны для написания кода, который можно многократно использовать вне зависимости от типов.
Безопасность типов
Когда обобщённый код (например, алгоритм) применяется с конкретным типом, компилятор гарантирует нам, что в алгоритме будут использоваться лишь те «объекты», что совместимы с этим типом данных. В случае, если попытаться передать не совместимый с кодом тип, то компилятор породит ошибку.
Более простой и понятный код
Поскольку компилятор защищает нас от передачи аргументов неправильных типов, это позволяет использовать меньше защитников типов, которые были рассмотрены в предыдущей статье. Однако, работает только в том случае, если ваш стек полностью построен на TypeScript и это не будет работать в том случае, если вы распространяете свой код в скомпилированном виде. В таком случае вы обязаны использовать защитники типов, если хотите оберегать своих пользователей от неправильного использования вашего кода.
Базовые ограничения обобщённых типов
Посмотрим на код ниже и попытаемся ответить на вопрос: что здесь может пойти не так?
Ограничения позволяют сузить перечень типов, которые можно будет передать в обобщённом аргументе, и расширяет возможности по работе с этими типами.
Пусть у нас есть три объекта, каждый из которых представлен своим интерфейсом, соответственно:
Теперь попробуем передать в нашу функцию объект из переменной a и b :
Уточнения с помощью обобщений
Все мы знаем, что типов и поколений покемонов так много, что без TypeScript-а и структур данных здесь разобраться не получится. Давайте напишем простейший интерфейс, описывающий покемонов: тип, вес, высота и сила атаки.
А теперь напишем функцию, которая умеет работать не только с интерфейсом покемонов, но и с другими интерфейсами, используя обобщения.
Врядли кто-то из нас будет писать функцию, которая просто возвращает значение свойств без каких-либо ещё действий и логики. Конструкция keyof – это гарант того, что вы пытаетесь передать в функцию только разрешённый объект и существующее имя свойства, при этом взамен вы получаете не только значение свойства, но и его тип.
Ошибок нет. Типов нет. Уверенности в том, что функция не просто работает, а работает правильно – нет. Именно для этого нужна статическая типизация – для того, чтобы быть уверенным в том, что ваша функция работает так, как вы и задумывали. Здесь лишь одна оговорочка: речь идёт только про возвращаемый тип, а не про поведение функции.
Обобщения и интерфейсы
Обобщения не обошли стороной и интерфейсы.
Но это скучно и понятно – давайте попробуем посмотреть на более сложный пример. Допустим, имеется интефейс, описывающий пользователя какого-нибудь простенького сервиса:
Однако не спешите писать такой тип в ваших проектах сами: тип Partial уже существует в TypeScript и не требует пользовательского определения.
Некоторые встроенные типы для обобщений
В функциональном программировании, да и в программировании вообще, часто встречается необходимость изъять из какого-либо объекта не значение одного свойства, а двух или нескольких. Например, это может быть «фильтр» данных. Напишем такой «фильтр», используя уже знакомые нам типы и структуры, в рамках всё тех же покемонов.
Код немного грязный, но я не смог придумать более элегантного решения с типами.
Основные типы для обобщений:
TypeScript имеет большое количество стандартных типов, которые могут использоваться в вашем коде с обобщениями. Полный список не описывается в документации, но вы можете почитать описание ES5. Также существует модуль typescript-collections, имплементирующий многие «стандартные» структуры данных на TypeScript.
Обобщения в классах
Здесь особо не о чем говорить, потому что всё то, что можно сделать с обобщениями в интерфейсах – можно сделать и в классах. Просто имейте в виду. Зато здесь можно поговорить об ограничениях.
Ссылочки
По традиции оставляю пару ссылочек, которые помогут понять материал не только с моей стороны, но и со стороны других авторов. Не удивляйтесь, что в ссылках присутствуют материалы по C#, так как TypeScript берёт многие возможности именно из него – напомню, что у них один автор.
Делимся на оплату хостинга или кофе.
Чем чаще пью кофе, тем чаще пишу статьи.
Школа магии TypeScript: дженерики и расширение типов
Автор статьи, перевод которой мы сегодня публикуем, говорит, что TypeScript — это просто потрясающе. Когда он только начал пользоваться TS, ему страшно нравилась та свобода, которая присуща этому языку. Чем больше сил программист вкладывает в свою работу со специфичными для TS механизмами — тем значительнее получаемые им выгоды. Тогда он использовал аннотации типов лишь периодически. Иногда он пользовался возможностями по автодополнению кода и подсказками компилятора, но, в основном, полагался лишь на собственное видение решаемых им задач.
В итоге он пришёл к выводу о том, что лучше так не делать. Он подружился с компилятором, начал обращать внимание на его подсказки. Компилятор находит проблемы в коде и сообщает о них задолго до того, как они могут нанести реальный вред. Автор статьи, глядя на себя как на разработчика, понял, что компилятор — это его лучший друг, так как защищает его от него самого. Как тут не вспомнить слова Альбуса Дамблдора: «Требуется большая храбрость, чтобы выступить против своих врагов, но не меньше ее требуется и чтобы выступить против своих друзей».
Каким бы хорошим другом ни был компилятор, ему не всегда легко угодить. Иногда избежать использования типа any очень непросто. А порой кажется, что any — это единственное разумное решение некоей проблемы.
Этот материал посвящён двум ситуациям. Избежав использования в них типа any можно обеспечить типобезопасность кода, открыть возможности по его повторному использованию и сделать его интуитивно понятным.
Дженерики
Как видно, у нас имеется хорошая функция, но в ней не используются аннотации типов, а их отсутствие означает и то, что такую функцию нельзя назвать типобезопасной. Исправим это.
Так наша функция выглядит уже гораздо лучше. Компилятор теперь знает тип ожидаемого от неё результата, это пригодится нам позже. Однако для того, чтобы добиться безопасной работы с типами, мы пожертвовали возможностями повторного использования функции. Что если нам когда-нибудь понадобится применять её для получения каких-то других сущностей? Не может быть, чтобы эту функцию нельзя было бы уже никак улучшить. И это действительно так.
В TypeScript, как и в других языках со строгой типизацией, мы можем использовать дженерики (generics), которые ещё называют «обобщёнными типами», «универсальными типами», «обобщениями».
Использование дженериков и ключевого слова keyof — это весьма мощный приём. Если вы пишете программы в IDE, которая поддерживает TypeScript, то, вводя аргументы, вы сможете воспользоваться возможностями автодополнения, что очень удобно.
Надеюсь, что сейчас у вас сформировалось абсолютно чёткое понимание того, как пользоваться дженериками в TypeScript, но если вы, чтобы совсем хорошо всё усвоить, хотите поэкспериментировать с рассмотренным здесь кодом, можете заглянуть сюда.
Расширение существующих типов
Итак, этот код работает:
Если вы собираетесь расширить интерфейс внешней библиотеки, то вам, скорее всего, понадобится доступ к пространству имён ( namespace ) этой библиотеки. Вот пример того, как добавить поле userId к Request из библиотеки Express :
Поэкспериментировать с кодом из этого раздела можно здесь.
Итоги
В этом материале мы рассмотрели методики использования дженериков и расширения типов в TypeScript. Надеемся то, что вы сегодня узнали, поможет вам в написании надёжного, понятного и типобезопасного кода.
Уважаемые читатели! Как вы относитесь к типу any в TypeScript?
Дженерики в TypeScript
Привет, я Сергей Вахрамов, занимаюсь фронтенд-разработкой на Angular в компании Тинькофф. Во фронтенд-разработку вошел напрямую с тайпскрипта, просто перечитав всю документацию. С того момента и спецификация ECMAScript расширилась, и TypeScript сильно подрос. Казалось бы, почему разработчики могут бояться дженериков, ведь бояться там нечего? Мой опыт общения с джуниор-разработчиками говорит, что во многом ребята не используют обобщенные типы просто потому, что кто-то пустил легенду об их сложности.
Эта статья для тех, кто не использует generic-типы в TypeScript: не знают о них, боятся использовать или используют вместо реальных типов — any .
Дженерики, или Generic Types, — обобщенные типы. Они нужны для описания похожих, но отличающихся какими-то характеристиками типов. Мы описываем общую структуру, а конкретную уже определяет пользователь дженерика. Дженерик — это каркас, внутренности которого заполняет разработчик. Программист, который описывает обобщенный тип, никогда не знает, что именно туда решит записать тот, кто будет этот тип использовать.
Посмотрим на пример использования дженериков в TypeScript. Представьте, что у нас есть массив значков валют. В JavaScript мы бы просто написали:
В TypeScript с помощью дженериков можно написать:
Оператор keyof
Это оператор, который берет все ключи объекта и представляет в виде числового или строкового литерального объединения.
Давайте представим, что нам с сервера шлют объект с такой структурой:
Если нам потребуются ключи из типа Payment, тут и пригодится оператор keyof.
Что дальше?
Мы дали основные знания, которые помогут уверенно использовать мощь обобщенных типов. Теперь можно переходить к Generic Types.
Случается, что в работе мы используем уже написанное до нас и не влияем на доработку кода.
Представим, что нам с сервера приходит описание платежа из истории в формате:
Потом кто-то разработал новый сервис, который отдает информацию о переводах. И он стал передавать информацию в следующем виде:
Теперь нам приходит код валюты, а не ее буквенное обозначение. При этом на старые записи в истории мы все еще получаем строковый код. Чтобы не описывать разные типы и не создавать путаницу, можно объединить их в один обобщенный тип — дженерик:
Можно указать типы параметров дженерика по умолчанию. Если не передать в такой дженерик параметр, то TypeScript возьмёт значение по умолчанию:
Возможно, пример не самый удачный, но он сделан для ввода в курс дела, дальше ждут более приближенные к реальной жизни примеры.
Типизация функций и методов
Некоторые из функций могут быть вызваны с разным количеством аргументов и их типами. В TypeScript такие функции можно описать с помощью перегрузок.
Допустим, у нас есть функция identity — она возвращает аргумент, который мы ей передали.
Но можно написать вот так:
В этом примере мы просто типизировали функцию (Function Declaration) через дженерик. Можно также типизировать функциональное выражение (Function Expression):
Типизация классов
Зачем может понадобиться обобщать классы? Уже на этапе разработки мы часто знаем, что один и тот же класс можно использовать для обслуживания разных данных. Например, выпадающему списку можно дать массив строк для отображения простого контента, а можно передать массив шаблонов для отрисовки сложного. Дженерики помогут описать такой класс.
Тот же самый класс будет выглядеть намного понятнее с TypeScript. Для начала опишем интерфейс:
Теперь напишем класс, который реализует наш интерфейс:
Ограничения дженериков. Generic Constraints
Иногда нужно как-то ограничить тип, принимаемый дженериком. Покажу на реальном примере.
Допустим, у нас есть функция для получения значения свойства length аргумента:
Если вы попробуете ее скомпилировать, получите ошибку:
Ошибка пропадет, а пример заработает. При этом разработчик, использующий функцию, будет точно понимать, какой аргумент требуется в нее передать.
Какой еще может быть практический пример? Давайте напишем функцию, которая принимает в себя объект и ключ, а выдает значение из переданного объекта по ключу:
В этом примере также показана возможность ограничения типа, используемого в объявлении функции, с помощью уже имеющегося параметра:
Охранники типов: Type Guards
Оператор typeof (typeof type guards). TypeScript — классный инструмент. Он позволяет выводить типы из конкретных переменных. Это называется Type Inference или «вывод типов».
Допустим, у нас уже есть переменная со ссылкой на объект, в котором записаны данные:
Если мы захотим вывести тип этой переменной в коде, то без проблем сделаем это при помощи typeof :
Почему typeof null === ‘object’? Это общепризнанное поведение JS. Многие считают его багом, попытаюсь объяснить почему.
Любая переменная хранит свое значение в виде последовательности битов. Из 32 бит, отведенных для хранения значения переменной, решили 3 выделить под хранение ее типа.
Оператор instanceof (instanceof type guards). Позволяет проверить, является ли данный объект экземпляром конкретного класса.
Тут ничего сложного. Для упрощения напишем классы без реализации и создадим экземпляры этих классов.
User-Defined Type Guards
В TypeScript есть еще один прекрасный инструмент — «определенные пользователем тайп-гарды».
Допустим, у нас есть следующие интерфейсы:
Мы можем легко это исправить. Достаточно лишь сказать TypeScript, что наша функция определяет, реализует ли переданный аргумент интерфейс Dog :
Теперь TypeScript не ругается, он понял тип переменной pet :
Сначала это может сложно восприниматься, поэтому подробнее про сужение типов предлагаю прочитать отдельно в документации.
Также хочу отметить, что при использовании Type Guard вся ответственность за определение типов лежит на разработчике. Он напрямую говорит TypeScript: «Это точно вот этот тип и никакой другой, я гарантирую».
В заключение
Мы узнали про дженерики и их параметризацию, научились с помощью них типизировать переменные, функции и методы, а также классы. Узнали, как можно ограничить типы и при необходимости помочь TypeScript с выведением типов, использовав Type Guard.
Если вы только начинаете использовать TypeScript в своей работе, начните с чтения официальной документации. Ее стоит изучить в первую очередь, просто сев и почитав несколько часов или, возможно, дней. Тогда сложится полное представление о языке. Вряд ли запомнится все и сразу, но уже будете знать, где что искать.
Для продвинутых пользователей языка рекомендую официальную книгу — TypeScript Handbook. Ее можно использовать как шпаргалку с паттернами, объяснением поведения системы вывода типов, описанием работы компилятора. Там раскрываются тонкие моменты, о которых разработчик не задумывается повседневно.
Если вы «познали дзен» в написании дженериков или просто хотите попрактиковаться на реальных примерах и набить руку, можете также порешать задачки Type Challenges в одноименном github-репозитории. В папке questions задачи разделены по сложности и пронумерованы. Под каждой есть ссылка на предлагаемые разработчиками решения — можете легко проверить себя.
Обобщения (Generics)
Из всего, что стало и ещё станет известным о типизированном мире, тем, кто только начинает свое знакомство с ним, тема, посвященная обобщениям (generics), может казаться наиболее сложной. Хотя данная тема, как и все остальные, обладает некоторыми нюансами, каждый из которых будет детально разобран, в реальности, рассматриваемые в ней механизмы очень просты и схватываются на лету. Поэтому приготовьтесь, к концу главы место занимаемое множеством вопросов, касающихся обобщений, займет желание сделать все пользовательские конструкции универсальными.
К счастью, в нашей реальности нашли решение не только относительно печатных станков, но и типов. Нежелания тратить усилия на постоянное описывание монолитных типов послужило причиной зарождения парадигмы обобщенного программирования.
Обобщенное программирование (Generic Programming) — это подход, при котором алгоритмы могут одинаково работать с данными, принадлежащими к разным типам данных без изменения декларации (описания типа).
В основе обобщенного программирования лежит такое ключевое понятие как обобщение. Обобщение (Generics) — это параметризированный тип позволяющий объявлять параметры типа, являющиеся временной заменой конкретных типов, конкретизация которых будет выполнена в момент создания экземпляра. Параметры типа, при условии соблюдения некоторых правил, можно использовать в большинстве операций, допускающих работу с обычными типами. Все это вместе дает повод сравнивать обобщенный тип с правильной версией печатного станка, чьи заменяемые валы, предназначенные для отпечатывания информации на проходящей через них бумаге, сопоставимы с параметрами типа.
В реальности обобщения позволяют сокращать количество преобразований (приведений) и писать многократно используемый код, при этом повышая его типобезопасность.
Этих примеров должно быть достаточно для образования отчетливого образа обобщений. Но прежде чем продолжить, стоит уточнить значения таких далеко не всем очевидных терминов, как: обобщенный тип, параметризированный тип и универсальная конструкция.
Для понимания этих терминов необходимо представить чертеж бумажного домика, в который планируется поселить пойманного на пикнике жука. Когда гипотетический жук мысленно располагается вне границ начерченного жилища, сопоставимого с типом, то оно предстает в виде обобщенного типа. Когда жук представляется внутри своей будущей обители, то о ней говорят как о параметризированном типе. Если же чертеж материализовался, хотя и в форму представленную обычной коробкой из-под печенья, то её называют универсальной конструкцией.
Другими словами, тип, определяющий параметр, обозначается как обобщенный тип. При обсуждении типов, представляемых параметрами типа, необходимо понимать, что они определены в параметризированном типе. Когда объявление обобщенного типа получило реализацию, то такую конструкцию, будь, то класс или функция, называют универсальной (универсальный класс, универсальная функция или метод).
Обобщения в TypeScript
В TypeScript обобщения могут быть указаны для типов, определяемых с помощью:
Указывается обобщение сразу после идентификатора типа. Это правило остается неизменным даже в тех случаях, когда идентификатор отсутствует (как в случае с безымянным классовым или функциональным выражением), или же и вовсе не предусмотрен (стрелочная функция).
Но прежде чем приступить к детальному рассмотрению, нужно уточнить, что правила для функций, функциональных выражений и методов идентичны. Правила для классов ничем не отличаются от правил для классовых выражений. Исходя из этого, все дальнейшие примеры будут приводиться исключительно на классах и функциях.
В случае, когда обобщение указанно псевдониму типа ( type ), область видимости параметров типа ограничена самим выражением.
Область видимости параметров типа при объявлении функции и функционального выражения, включая стрелочное, а также методов, ограничивается их сигнатурой и телом. Другими словами, параметр типа можно использовать в качестве типа при объявлении параметров, возвращаемого значения, а также в допустимых выражениях (аннотация типа, приведение типа и т.д.) расположенных в теле.
При объявлении классов (в том числе классовых выражений) и интерфейсов, область видимости параметров типа ограничиваются областью объявления и телом.
В случаях, когда класс/интерфейс расширяет другой класс/интерфейс, который объявлен как обобщенный, потомок обязан указать типы для своего предка. Потомок в качестве аргумента типа может указать своему предку не только конкретный тип, но и тип, представляемый собственными параметрами типа.
Если класс/интерфейс объявлен как обобщенный, а внутри него объявлен обобщенный метод, имеющий идентичный параметр типа, то последний в своей области видимости будет перекрывать первый (более конкретно это поведение будет рассмотрено позднее).
Принадлежность параметра типа к конкретному типу данных устанавливается в момент передачи аргументов типа. При этом конкретные типы данных указываются в паре угловых скобок, а количество конкретных типов должно соответствовать количеству обязательных параметров типа.
Если обобщенный тип указывается в качестве типа данных, то он обязан содержать аннотацию обобщения (исключением является параметры типа по умолчанию, которые рассматриваются далее в главе).
Относительно обобщенных типов существуют такие понятия, как открытый (open) и закрытый (closed) тип. Обобщенный тип в момент определения называется открытым.
Кроме того, обобщенные типы, указанные в аннотации у которых хотя бы один из аргументов типа является параметром типа, также являются открытыми типами.
И наоборот, если все аргументы типа принадлежат к конкретным типам, то такой обобщенный тип является закрытым типом.
Те же самые правила применимы и к функциям, но за одним исключением — вывод типов для примитивных типов определяет принадлежность параметров типа к литеральным типам данных.
И опять, эти же правила верны и для функций.
В случаях, когда обобщенный класс содержит обобщенный метод, параметры типа метода будут затенять параметры типа класса.
Стоит заметить, что в TypeScript нельзя создавать экземпляры типов представляемых параметрами типа.
Кроме того, два типа, определяемые классом или функцией, считаются идентичными вне зависимости от того, являются они обобщенными или нет.
Механизм расширения требуется в тех случаях, в которых параметр типа должен обладать заданными характеристиками, необходимыми для выполнения конкретных операций над этим типом.
Для примера рассмотрим случай, когда в коллекции T ( Collection ) объявлен метод получения элемента по имени ( getItemByName ).
Пример, когда параметр типа расширяет другой параметр типа, будет рассмотрен немного позднее.
Также не лишним будет заметить, что когда параметр типа расширяет другой тип, в качестве аргумента типа можно будет передать только совместимый с ним тип.
Кроме того, расширять можно любые подходящие для этого типы, полученные любым доступным путем.
Помимо прочего, TypeScript позволяет указывать для параметров типа значение по умолчанию.
Кроме того, можно совмещать механизм установки значения по умолчанию и механизм расширения типа. В этом случае оператор равно = указывается после расширяемого типа.
В момент, когда тип T расширяет другой тип, он получает признаки этого типа. Именно поэтому для параметра типа, расширяющего другой тип, в качестве типа по умолчанию можно указывать только совместимый с ним тип.
Чтобы было проще понять, нужно представить два класса, один из которых расширяет другой. В этом случае переменной с типом суперкласса можно в качестве значения присвоить объект его подкласса, но — не наоборот.
Тот же самый механизм используется для параметров типа.
Тип, который указывается параметру типа в качестве типа по умолчанию, вообще ничего не ограничивает.
Не будет лишним также рассмотреть отличия этих двух механизмов при работе вывода типов.
Если обобщенная коллекция в качестве аргумента типа получает тип объединение ( Union ), то все её элементы будут принадлежать к типу объединения. Простыми словами, элемент из такой коллекции не будет, без явного преобразования, совместим ни с одним из вариантов, составляющих тип объединение.
Но операцию приведения типов можно поместить (сокрыть) прямо в метод самой коллекции и тем самым упростить её использование. Для этого метод должен быть обобщенным, а его параметр типа, указанный в качестве возвращаемого из функции, расширять параметр типа самой коллекции.
Сокрытие приведения типов прямо в методе коллекции повысило “привлекательность” кода. Но, все же, в случаях, когда элемент коллекции присваивается конструкции без явной аннотации типа, появляется потребность вызывать обобщенный метод с аргументами типа.
Кроме того, нужно не забывать, что два разных объявления параметров типа несовместимы, даже если у них идентичные идентификаторы.
