Отличный перевод с примерами, typescript handbook, на русский язык

http://typescript-lang.ru/docs/Generics.html

Advanced Types

Для начала создадим традиционную для знакомства c обобщениями первую функцию: функцию-тождество. Такая функция возвращает в точности то, что ей было передано. Можно расценивать ее так же, как командуecho.

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

function identity(arg: number): number {
    return arg;
}

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

function identity(arg: any): any {
    return arg;
}

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

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

function identity<t>(arg: T): T {
    return arg;
}

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

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

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

let output = identity<string>("myString");  // у output будет тип string

В этом примереTявно устанавливается вstring, как один из аргументов функции, но окруженный угловыми скобками<>вместо круглых().

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

let output = identity("myString");  // у output будет тип string

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

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

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

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

function identity<t>(arg: T): T {
    return arg;
}

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

function loggingIdentity<t>(arg: T): T {
    console.log(arg.length);  // Ошибка: у T нет свойства .length
    return arg;
}

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

Допустим, что на самом деле функция должна работать с массивами объектовT, а не с самими объектамиTнапрямую. Так как она будет иметь дело с массивами, у них должно быть свойство.length. Можно описать это так, словно мы создаем массив:

function loggingIdentity<t>(arg: T[]): T[] {
    console.log(arg.length);  // У массива есть .length, поэтому ошибки больше нет
    return arg;
}

ТипloggingIdentityчитается как "обобщенная функцияloggingIdentity, которая принимает типовый параметрTи аргументarg, который является массивом объектовT, и возвращает массив объектовT". Если функции будет передан массив чисел, то результатом также будет массив чисел, так какTстанетnumber. Это позволяет использовать обобщенную типовую переменнуюTкак часть типа, с которым мы работаем, а не только как целый тип, что дает большую гибкость.

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

function loggingIdentity<t>(arg: Array<t>): Array<t> {
    console.log(arg.length);  // У массива есть .length, поэтому ошибки больше нет
    return arg;
}

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

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

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

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

function identity<t>(arg: T): T {
    return arg;
}

let myIdentity: <t>(arg: T) => T = identity;

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

function identity<t>(arg: T): T {
    return arg;
}

let myIdentity: <U>(arg: U) => U = identity;

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

function identity<t>(arg: T): T {
    return arg;
}

let myIdentity: {<t>(arg: T): T} = identity;

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

interface GenericIdentityFn {
    <t>(arg: T): T;
}

function identity<t>(arg: T): T {
    return arg;
}

let myIdentity: GenericIdentityFn = identity;

Возможно, нам захотелось бы сделать обобщенный параметр параметром интерфейса в целом. Такой подход позволит понимать, относительно какого типа (или типов) происходит обобщение (то есть относительно Dictionary<string>

, а не простоDictionary). Это делает типовый параметр доступным всем остальным членам интерфейса.

interface GenericIdentityFn<t> {
    (arg: T): T;
}

function identity<t>(arg: T): T {
    return arg;
}

let myIdentity: GenericIdentityFn<number> = identity;

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

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

Если будет интересно смотрите больше по ссыке: http://typescript-lang.ru/docs/Generics.html

results matching ""

    No results matching ""