typescript что такое generic

Table of Contents #

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

В таких языках, как C# и Java, одним из главных инструментов для создания компонентов, предназначенных для повторного использования, являются обобщения (также известные как шаблоны или дженерики). Они позволяют создавать компоненты, способные работать с различными типами, а не только с каким-то одним. Это позволяет применять такие компоненты и использовать свои типы.

Здравствуй, мир обобщений! #

Без использования обобщений пришлось бы задать такой функции определенный тип:

Или же описать ее, используя тип any :

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

Мы добавили типовую переменную T к функции-тождеству. Эта T позволяет сохранять тип, который указал пользователь (то есть number ), так что позже его можно будет использовать. В данном случае T используется в качестве типа возвращаемого значения. Можно увидеть, что теперь и аргумент, и возвращаемое значение имеют один и тот же тип. Такой способ позволяет направить информацию о типах со входа функции к ее выходу.

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

Второй способ, вероятно, наиболее популярен. Здесь используется выведение типового аргумента, и компилятор автоматически устанавливает T на основании типа аргумента, который передается в функцию:

Обратите внимание, что тип не передается явно, в угловых скобках ( <> ) — компилятор просто проанализировал значение «myString» и установил T в значение его типа. Хотя выведение типового аргумента может быть полезно, чтобы сделать код более кратким и читаемым, иногда может понадобиться явно передавать типовый аргумент, если компилятору не удается автоматически вывести тип, что может произойти в более сложных случаях.

Работа с обобщенными типовыми переменными #

Возьмем уже знакомую нам функцию identity :

Что, если нужно при каждом вызове функции выводить длину аргумента arg в консоль? Может появиться искушение написать так:

Как вариант, можно записать этот пример следующим способом:

Обобщенные типы #

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

Тип обобщенной функции схож с типом обычной функции, где типовый параметр указан первым, так же, как и в ее определении:

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

Также можно записать обобщенный тип как сигнатуру вызова на типе объектного литерала:

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

Обратите внимание, что пример трансформировался в нечто совершенно иное. Вместо описания обобщенной функции теперь обычная, не обобщенная функция, которая является частью обобщенного типа. При использовании GenericIdentityFn теперь придется указывать соответствующий типовый аргумент (в данном случае number ), таким образом зафиксировав типы, которые будет использовать соответствующая функция. Понимать, в каких случаях типовый параметр нужно добавлять к сигнатуре вызова, а когда — к самому интерфейсу, полезно при описании того, какие аспекты типа являются обобщенными.

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

Обобщенные классы #

Обобщенные классы имеют такой же вид, что и обобщенные интерфейсы. У них есть список типовых параметров в угловых скобках ( <> ) после имени класса.

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

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

Ограничения обобщений #

Поскольку обобщенная функция теперь имеет ограничение, она не сможет работать с любым типом:

Вместо этого ей необходимо передавать значения тех типов, у которых есть все необходимые свойства:

Использование типовых параметров в ограничениях обобщений

Можно объявить типовый параметр, который ограничивается другим типовым параметром. К примеру, нужно принять два объекта и копировать свойства из одного в другой. Нужно удостовериться, что мы случайно не добавим какое-либо лишнее свойство, поэтому добавим ограничение между двумя типами:

Использование типов классов в обобщениях

Реализуя паттерн «фабрика» с использованием обобщений, необходимо указывать на тип класса с помощью его функции-конструктора. К примеру,

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

Источник

ТипScript для начинающих, часть 5: Generics

Russian (Pусский) translation by Ilya Nikov (you can also view the original English article)

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

Потребность в генериках

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

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

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

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

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

Читайте также:  Что такое малиновый цвет

Использование дженериков может показаться очень ограниченным

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

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

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

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

Создание общих функций с использованием ограничений

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

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

Заключение

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

Если у вас есть какие-либо вопросы, связанные с этим руководством, я буду рад ответить на них в комментариях.

Источник

Typescript что такое generic

A major part of software engineering is building components that not only have well-defined and consistent APIs, but are also reusable. Components that are capable of working on the data of today as well as the data of tomorrow will give you the most flexible capabilities for building up large software systems.

In languages like C# and Java, one of the main tools in the toolbox for creating reusable components is generics, that is, being able to create a component that can work over a variety of types rather than a single one. This allows users to consume these components and use their own types.

Hello World of Generics

To start off, let’s do the “hello world” of generics: the identity function. The identity function is a function that will return back whatever is passed in. You can think of this in a similar way to the echo command.

Without generics, we would either have to give the identity function a specific type:

Or, we could describe the identity function using the any type:

Instead, we need a way of capturing the type of the argument in such a way that we can also use it to denote what is being returned. Here, we will use a type variable, a special kind of variable that works on types rather than values.

We’ve now added a type variable Type to the identity function. This Type allows us to capture the type the user provides (e.g. number ), so that we can use that information later. Here, we use Type again as the return type. On inspection, we can now see the same type is used for the argument and the return type. This allows us to traffic that type information in one side of the function and out the other.

Once we’ve written the generic identity function, we can call it in one of two ways. The first way is to pass all of the arguments, including the type argument, to the function:

The second way is also perhaps the most common. Here we use type argument inference — that is, we want the compiler to set the value of Type for us automatically based on the type of the argument we pass in:

Working with Generic Type Variables

Let’s take our identity function from earlier:

What if we want to also log the length of the argument arg to the console with each call? We might be tempted to write this:

We can alternatively write the sample example this way:

In previous sections, we created generic identity functions that worked over a range of types. In this section, we’ll explore the type of the functions themselves and how to create generic interfaces.

The type of generic functions is just like those of non-generic functions, with the type parameters listed first, similarly to function declarations:

We could also have used a different name for the generic type parameter in the type, so long as the number of type variables and how the type variables are used line up.

We can also write the generic type as a call signature of an object literal type:

Which leads us to writing our first generic interface. Let’s take the object literal from the previous example and move it to an interface:

In a similar example, we may want to move the generic parameter to be a parameter of the whole interface. This lets us see what type(s) we’re generic over (e.g. Dictionary rather than just Dictionary ). This makes the type parameter visible to all the other members of the interface.

In addition to generic interfaces, we can also create generic classes. Note that it is not possible to create generic enums and namespaces.

A generic class has a similar shape to a generic interface. Generic classes have a generic type parameter list in angle brackets ( <> ) following the name of the class.

This is a pretty literal use of the GenericNumber class, but you may have noticed that nothing is restricting it to only use the number type. We could have instead used string or even more complex objects.

Читайте также:  volupta для женщин что это

Just as with interface, putting the type parameter on the class itself lets us make sure all of the properties of the class are working with the same type.

As we cover in our section on classes, a class has two sides to its type: the static side and the instance side. Generic classes are only generic over their instance side rather than their static side, so when working with classes, static members can not use the class’s type parameter.

Because the generic function is now constrained, it will no longer work over any and all types:

Instead, we need to pass in values whose type has all the required properties:

Using Type Parameters in Generic Constraints

Using Class Types in Generics

When creating factories in TypeScript using generics, it is necessary to refer to class types by their constructor functions. For example,

A more advanced example uses the prototype property to infer and constrain relationships between the constructor function and the instance side of class types.

This pattern is used to power the mixins design pattern.

Creating Types from Types

An overview of the ways in which you can create more types from existing types.

Keyof Type Operator

Using the keyof operator in type contexts.

The TypeScript docs are an open source project. Help us improve these pages by sending a Pull Request ❤

Источник

Дженерики в TypeScript: разбираемся вместе

Всем привет! Команда TestMace публикует очередной перевод статьи из мира web-разработки. На этот раз для новичков! Приятного чтения.

Развеем пелену таинственности и недопонимания над синтаксисом и наконец подружимся с ним

Наверное, только матёрые разработчики Java или других строго типизированных языков не хлопают глазами, увидев дженерик в TypeScript. Его синтаксис коренным образом отличается от всего того, что мы привыкли видеть в JavaScript, поэтому так непросто сходу догадаться, что он вообще делает.

Я бы хотел показать вам, что на самом деле всё гораздо проще, чем кажется. Я докажу, что если вы способны реализовать на JavaScript функцию с аргументами, то вы сможете использовать дженерики без лишних усилий. Поехали!

Дженерики в TypeScript

В документации TypeScript приводится следующее определение: «дженерики — это возможность создавать компоненты, работающие не только с одним, а с несколькими типами данных».

Здорово! Значит, основная идея состоит в том, что дженерики позволяют нам создавать некие повторно используемые компоненты, работающие с различными типами передаваемых им данных. Но как это возможно? Вот что я думаю.

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

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

Сделаем так, чтобы она работала с числами:

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

Посмотрите на вызов функции. Теперь-то синтаксис дженериков не должен вас пугать. T и U — это просто имена переменных, которые вы назначаете сами. При вызове функции вместо них указываются типы, с которыми будет работать данная функция.

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

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

Обратите особое внимание на второй вызов console.log на анимации выше — в него не передаётся тип. В этом случае TypeScript попытается вычислить тип по переданным данным.

Обобщённые классы и интерфейсы

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

Посмотрите на пример и попробуйте разобраться сами. Надеюсь, у вас получилось.

Если код сразу не понятен, попробуйте отследить значения type сверху вниз вплоть до вызовов функции. Порядок действий следующий:

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

Результатом выполнения данного кода будет:

Подведем итоги

Надеюсь, я помог вам разобраться с дженериками. Запомните, всё, что вам нужно сделать, — это всего лишь передать значение type в функцию 🙂

Если хотите ещё почитать про дженерики, я прикрепил далее пару ссылок.

Источник

Обобщения (Generics)

Из всего, что стало и ещё станет известным о типизированном мире, тем, кто только начинает свое знакомство с ним, тема, посвященная обобщениям (generics), может казаться наиболее сложной. Хотя данная тема, как и все остальные, обладает некоторыми нюансами, каждый из которых будет детально разобран, в реальности, рассматриваемые в ней механизмы очень просты и схватываются на лету. Поэтому приготовьтесь, к концу главы место занимаемое множеством вопросов, касающихся обобщений, займет желание сделать все пользовательские конструкции универсальными.

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

Обобщенное программирование (Generic Programming) — это подход, при котором алгоритмы могут одинаково работать с данными, принадлежащими к разным типам данных без изменения декларации (описания типа).

В основе обобщенного программирования лежит такое ключевое понятие как обобщение. Обобщение (Generics) — это параметризированный тип позволяющий объявлять параметры типа, являющиеся временной заменой конкретных типов, конкретизация которых будет выполнена в момент создания экземпляра. Параметры типа, при условии соблюдения некоторых правил, можно использовать в большинстве операций, допускающих работу с обычными типами. Все это вместе дает повод сравнивать обобщенный тип с правильной версией печатного станка, чьи заменяемые валы, предназначенные для отпечатывания информации на проходящей через них бумаге, сопоставимы с параметрами типа.

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

Читайте также:  Что такое мультипликатор в акциях

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

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

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

Обобщения в TypeScript

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

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

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

В случае, когда обобщение указанно псевдониму типа ( type ), область видимости параметров типа ограничена самим выражением.

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

При объявлении классов (в том числе классовых выражений) и интерфейсов, область видимости параметров типа ограничиваются областью объявления и телом.

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

Если класс/интерфейс объявлен как обобщенный, а внутри него объявлен обобщенный метод, имеющий идентичный параметр типа, то последний в своей области видимости будет перекрывать первый (более конкретно это поведение будет рассмотрено позднее).

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

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

Относительно обобщенных типов существуют такие понятия, как открытый (open) и закрытый (closed) тип. Обобщенный тип в момент определения называется открытым.

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

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

Те же самые правила применимы и к функциям, но за одним исключением — вывод типов для примитивных типов определяет принадлежность параметров типа к литеральным типам данных.

И опять, эти же правила верны и для функций.

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

Стоит заметить, что в TypeScript нельзя создавать экземпляры типов представляемых параметрами типа.

Кроме того, два типа, определяемые классом или функцией, считаются идентичными вне зависимости от того, являются они обобщенными или нет.

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

Для примера рассмотрим случай, когда в коллекции T ( Collection ) объявлен метод получения элемента по имени ( getItemByName ).

Пример, когда параметр типа расширяет другой параметр типа, будет рассмотрен немного позднее.

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

Кроме того, расширять можно любые подходящие для этого типы, полученные любым доступным путем.

Помимо прочего, TypeScript позволяет указывать для параметров типа значение по умолчанию.

Кроме того, можно совмещать механизм установки значения по умолчанию и механизм расширения типа. В этом случае оператор равно = указывается после расширяемого типа.

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

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

Тот же самый механизм используется для параметров типа.

Тип, который указывается параметру типа в качестве типа по умолчанию, вообще ничего не ограничивает.

Не будет лишним также рассмотреть отличия этих двух механизмов при работе вывода типов.

Если обобщенная коллекция в качестве аргумента типа получает тип объединение ( Union ), то все её элементы будут принадлежать к типу объединения. Простыми словами, элемент из такой коллекции не будет, без явного преобразования, совместим ни с одним из вариантов, составляющих тип объединение.

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

Сокрытие приведения типов прямо в методе коллекции повысило “привлекательность” кода. Но, все же, в случаях, когда элемент коллекции присваивается конструкции без явной аннотации типа, появляется потребность вызывать обобщенный метод с аргументами типа.

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

Источник

Информ портал о технике и не только