В этом руководстве вы узнаете об обобщениях (дженериках) в TypeScript, которые позволяют использовать типы в качестве формальных параметров.
Введение в дженерики TypeScript
Дженерики TypeScript позволяют вам писать повторно используемые и обобщенные формы функций, классов и интерфейсов. В этом руководстве вы сосредоточитесь на разработке универсальных функций.
Объяснить дженерики TypeScript будет проще на простом примере.
Предположим, вам нужно разработать функцию, которая возвращает случайный элемент в массиве чисел.
Следующая функция getRandomNumberElement()
принимает в качестве параметра массив чисел и возвращает случайный элемент из массива:
function getRandomNumberElement(items: number[]): number { let randomIndex = Math.floor(Math.random() * items.length); return items[randomIndex]; }
Чтобы получить случайный элемент массива, вам нужно:
- Сначала найти случайный индекс.
- Получить случайный элемент на основе случайного индекса.
Чтобы найти случайный индекс массива, мы использовали Math.random()
, который возвращает случайное число от 0 до 1, умножили его на длину массива и применили к результату Math.floor()
.
Ниже показано, как использовать функцию getRandomNumberElement()
:
let numbers = [1, 5, 7, 4, 2, 9]; console.log(getRandomNumberElement(numbers));
Предположительно, вам нужно получить случайный элемент из массива строк. На этот раз вы можете придумать новую функцию:
function getRandomStringElement(items: string[]): string { let randomIndex = Math.floor(Math.random() * items.length); return items[randomIndex]; }
Логика функции getRandomStringElement()
такая же, как и в функции getRandomNumberElement()
.
В этом примере показано, как использовать функцию getRandomStringElement()
:
let colors = ['red', 'green', 'blue']; console.log(getRandomStringElement(colors));
Позже вам может понадобиться получить случайный элемент в массиве объектов. Создание новой функции каждый раз, когда вы хотите получить случайный элемент из массива нового типа, не является масштабируемым.
Использование типа any
Одним из решений этой проблемы является установка такого типа аргумента массива как any[]
. Для этого вам нужно написать всего одну функцию, которая работает с массивом любого типа.
function getRandomAnyElement(items: any[]): any { let randomIndex = Math.floor(Math.random() * items.length); return items[randomIndex]; }
getRandomAnyElement()
работает с массивом типа any
, включающим в себя массив чисел, строк, объектов и т. д.:
let numbers = [1, 5, 7, 4, 2, 9]; let colors = ['red', 'green', 'blue']; console.log(getRandomAnyElement(numbers)); console.log(getRandomAnyElement(colors));
Это решение работает нормально. Однако у него есть недостаток.
Это не позволяет вам применять тип возвращаемого элемента. Другими словами, это небезопасно для типов.
Лучшим решением избежать дублирования кода при сохранении типа является использование дженериков.
На помощь приходят дженерики TypeScript
Ниже показана универсальная функция, которая возвращает случайный элемент из массива типа T
:
function getRandomElement<T>(items: T[]): T { let randomIndex = Math.floor(Math.random() * items.length); return items[randomIndex]; }
Эта функция использует переменную типа T
. T
позволяет зафиксировать тип, предоставленный во время вызова функции. Кроме того, функция использует переменную типа T
в качестве типа возвращаемого значения.
Эта функция getRandomElement()
является универсальной, поскольку она может работать с любым типом данных, включая строку, число, объекты и т. д.
По соглашению мы используем букву T
в качестве переменной типа. Однако вы можете свободно использовать другие буквы, такие как A
, B
C
, …
Вызов универсальной функции
Ниже показано, как использовать getRandomElement()
с массивом чисел:
let numbers = [1, 5, 7, 4, 2, 9]; let randomEle = getRandomElement<number>(numbers); console.log(randomEle);
В этом примере number
в качестве типа T
явно передается в функцию getRandomElement()
.
На практике вы будете использовать вывод типа для аргумента. Это означает, что вы позволяете компилятору TypeScript автоматически устанавливать значение T
в зависимости от типа аргумента, который вы передаете, например так:
let numbers = [1, 5, 7, 4, 2, 9]; let randomEle = getRandomElement(numbers); console.log(randomEle);
В этом примере мы не передавали тип number
в getRandomElement()
явно. Компилятор просто смотрит на аргумент и устанавливает T
в его тип.
Теперь функция getRandomElement()
также является типобезопасной. Например, если вы присвоите возвращаемое значение строковой переменной, вы получите сообщение об ошибке:
let numbers = [1, 5, 7, 4, 2, 9]; let returnElem: string; returnElem = getRandomElement(numbers); // compiler error
Общие функции с несколькими типами
Ниже показано, как разработать универсальную функцию с двумя переменными типа U
и V
:
function merge<U, V>(obj1: U, obj2: V) { return { ...obj1, ...obj2 }; }
Функция merge()
объединяет два объекта с типами U
и V
. Она таккже объединяет свойства двух объектов в один объект.
Вывод типа выводит возвращаемое значение функции merge()
как тип пересечения U
и V
, то есть U & V.
Ниже показано, как использовать функцию merge()
, которая объединяет два объекта:
let result = merge( { name: 'John' }, { jobTitle: 'Frontend Developer' } ); console.log(result);
Выход:
{ name: 'John', jobTitle: 'Frontend Developer' }
Преимущества дженериков TypeScript
Ниже приведены преимущества дженериков TypeScript:
- Проверка типа во время компиляции.
- Исключают приведения типа.
- Позволяют реализовать общие алгоритмы.
Заключение
- Используйте универсальные шаблоны TypeScript для разработки многократно используемых, обобщенных и типобезопасных функций, интерфейсов и классов.