Главная Категории Контакты Поиск

Универсальные шаблоны или Generics в TypeScript

Зачем и когда создавать универсальные шаблоны и как их использовать в TypeScript.

TypeScript·10.10.2019·читать 2 мин 🤓·Автор: Alexey Myzgin

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

Универсальные шаблоны чаще всего появляются во вспомогательных функциях. Например: у нас есть функция fill, которая принимает массив array и значение value. Она заполнит массив любым значением, которое мы передадим. Поскольку это вспомогательная функция, мы не знаем, какого типа будет это значение. Это может быть любой тип. Но, мы уверены, что функция вернет массив, полный значений этого типа.

Реализация достаточно простая: мы перебираем массив и записываем в него значение.

function fill(array, value){
  return array.map(() => value)
}

Так как мы не знаем тип значения value, то указываем тип value: IDontKnow. IDontKnow - это тип. Мы не определяем этот тип, а делаем его универсальным:

function fill<IDontKnow>(array, value: IDontKnow){
  return array.map(() => value)
}

Мы нигде не объявляем тип IDontKnow, а просто говорим, что эта функция будет иметь тип <IDontKnow>, о котором мы пока не знаем. Наш массив будет иметь тип - массив с любым значением array: any[]. Функция в свою очередь будет возвращать тип - массив значений которых мы не знаем IDontKnow[].

function fill<IDontKnow>(array: any[], value: IDontKnow): IDontKnow[] {
  return array.map(() => value);
}

Вызываем функцию, передаем ей массив [1, 2, 3] и значение "a":

function fill<IDontKnow>(array: any[], value: IDontKnow): IDontKnow[] {
  return array.map(() => value);
}

const result = fill([1, 2, 3], "a");

Если мы сейчас наведем курсор на вызов функции fill([1, 2, 3], "a"), то увидим подсказку:

function fill<string>(array: any[], value: string): string[]

Поскольку при вызове функции fill в качестве второго аргумента value мы передаем строку, то видим, что fill<string>, принимает массив с любыми значениями, значение value: string и возвращается массив строк : string[].

Так как TypeScript знает что функция возвращает массив строк, мы можем вызвать toUpperCase() на каждом элементе массива.

function fill<IDontKnow>(array: any[], value: IDontKnow): IDontKnow[] {
  return array.map(() => value);
}

const result = fill([1, 2, 3], "a");
result.map(x => x.toUpperCase());

Давай попробуем другой вариант. Заполним массив числами:

function fill<IDontKnow>(array: any[], value: IDontKnow): IDontKnow[] {
  return array.map(() => value);
}

const values = fill(["a", "b", "c"], 4)

Теперь, когда наведем на вызов функции, то увидим что - значение value: number и функция возвращает массив чисел number[].

function fill<number>(array: any[], value: number): number[]

Поэтому можем сделать так:

function fill<IDontKnow>(array: any[], value: IDontKnow): IDontKnow[] {
  return array.map(() => value);
}

const values = fill(["a", "b", "c"], 4);

values.map(x => x / 2);

TypeScript не будет ругаться, так как он знает, что values это массив чисел.

Если попытаемся вызвать toUpperCase() в values, то получим ошибку - Property 'toUpperCase' does not exist on type 'number'.

Таким образом, даём TypeScript достаточно информации о том, что мы можем сделать с результатом. Мы передаем тип при вызове функции, а затем используем этот тип во всей функции.

Обычно, по согласованию, используемся буква Т, вот так:

function fill<T>(array: any[], value: T): T[] {
  return array.map(() => value);
}

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

Website, name & logo
Copyright © 2022. Alex Myzgin