Если вы только начинаете работать с TypeScript, возможно, вы слышали термин «дженерики», но не знаете, что он означает. Проще говоря, обобщения позволяют вам писать код, который работает с различными типами данных, делая ваш код более универсальным и гибким. Хотя поначалу понимание концепции может сбивать с толку, как только вы поймете, что такое универсальные шаблоны, они могут сэкономить вам много времени и усилий при написании кода.
Синтаксис для обобщений TypeScript
В TypeScript Generics используют угловые скобки (<>)
для определения типа заполнителя, который можно применять к функциям, классам и интерфейсам. Например, вы можете создать функцию, которая складывает два числа и объединяет две строки, используя один и тот же код. Вот пример универсальной функции, которая складывает два числа:
function add<T>(a: T, b: T): T { return a + b; }
В этом примере T
— это тип заполнителя, который можно заменить любым типом данных. Функция принимает два аргумента типа T
, складывает их вместе и возвращает результат типа T
. Вот как вы можете вызывать функцию с разными типами данных:
const sum1 = add<number>(2, 3); // returns 5 const sum2 = add<string>('Rabi', ' Siddique'); // returns 'Rabi Siddique'
В первом примере number
передается как параметр универсального типа T
, и функция возвращает число. Во втором примере string
передается как параметр универсального типа T
, и функция возвращает строку.
Зачем использовать дженерики TypeScript?
Допустим, вы пишете функцию, которая сортирует массив чисел. Вы можете написать что-то вроде этого:
function sortNumbers(arr: number[]): number[] { // ... sorting logic ... return arr; }
Эта функция принимает массив чисел в качестве аргумента и возвращает отсортированный массив чисел. Но что, если вы хотите отсортировать массив строк? Для этого нужно написать отдельную функцию:
function sortStrings(arr: string[]): string[] { // ... sorting logic ... return arr; }
Это быстро становится утомительным, особенно при работе с множеством разных типов данных. Здесь на помощь приходят дженерики TypeScript. С помощью дженериков вы можете написать одну функцию, которая работает с любым типом данных. Вот пример:
function sortArray<T>(arr: T[]): T[] { // ... sorting logic ... return arr; }
Итак, если вы вызовете эту функцию с массивом чисел, например:
sortArray<number>([3, 1, 4, 1, 5]); sortArray<string>(["banana", "apple", "pear", "orange"]);
В первом примере number
передается как параметр универсального типа T
, и функция соответствующим образом сортирует массив. Во втором примере string
передается как параметр универсального типа T
, и функция сортирует массив строк.
В этом примере TypeScript Generics сделал ваш код более адаптируемым к изменяющимся требованиям, поскольку вы можете просто поменять тип данных без необходимости переписывать весь код.
Использование обобщений TypeScript с классами
TypeScript Generics также можно использовать с классами для создания повторно используемых классов, которые могут работать с разными типами. Чтобы использовать Generics с классом, вы можете определить параметр типа внутри угловых скобок <>
после имени класса. Это создает заполнитель для типа данных, с которым будет работать класс. Вот пример того, как определить универсальный класс с помощью TypeScript Generics:
class List<T> { private items: T[] = []; add(item: T) { this.items.push(item); } getItems(): T[] { return this.items; } }
В этом коде параметр типа T
представляет тип элементов в списке. Класс List
имеет два метода: add
и getItems
. Метод add
принимает элемент типа T
в качестве аргумента и добавляет его в массив items
. Метод getItems
возвращает массив items
.
Чтобы использовать этот общий класс, просто укажите фактический тип данных в качестве аргумента. Вот пример использования класса List
:
const numbers = new List<number>(); numbers.add(1); numbers.add(2); numbers.add(3); console.log(numbers.getItems()); // [1, 2, 3] const strings = new List<string>(); strings.add("Rabi"); strings.add("Siddique"); console.log(strings.getItems()); // ["Rabi", "Siddique"]
В этом коде мы создаем два экземпляра класса List
, один для чисел и один для строк. Используя параметр типа T
для указания фактического типа данных для каждого экземпляра класса List
, мы можем повторно использовать один и тот же класс для разных типов данных без написания отдельных реализаций для каждого типа данных.
Использование обобщений TypeScript с интерфейсами
Если вы используете TypeScript, вы можете использовать Generics with Interfaces для создания многократно используемых интерфейсов, которые могут работать с разными типами. Параметр типа выступает в качестве заполнителя для фактического типа, который будет использоваться с интерфейсом. Вот пример того, как определить универсальный интерфейс:
interface Box<T> { value: T; }
В этом примере интерфейс Box
имеет параметр универсального типа T
. Свойство value интерфейса Box
имеет тип T
, что означает, что оно может содержать любое значение любого типа.
Чтобы использовать этот интерфейс, вы можете указать фактический тип данных в качестве аргумента. Вот пример использования Box
interface:
const stringBox: Box<string> = { value: 'Rabi Siddique' }; const numberBox: Box<number> = { value: 27 };
В этом коде мы создаем два экземпляра Box
interface, один для строк и один для чисел.
Вы также можете использовать параметр универсального типа для создания интерфейсов с несколькими свойствами одного типа. Вот пример:
interface Pair<T> { first: T; second: T; }
В этом примере интерфейс Pair
имеет параметр типа T
. Свойства first
и second
интерфейса Pair
имеют тип T
, что означает, что они могут содержать любое значение одного и того же типа. Когда вы создаете объект Pair
, вы можете указать тип, который будет использоваться с интерфейсом:
const stringPair: Pair<string> = { first: 'Rabi', second: 'Siddique' }; const numberPair: Pair<number> = { first: 27, second: 24 };
Общие ограничения TypeScript
Ограничения TypeScript Generics позволяют указать тип или набор типов, которые может принимать параметр типа. Это делается с помощью ключевого слова extends для указания ограничения. Например, давайте определим универсальную функцию, которая принимает только строки и числа и выводит их значение:
function printValue<T extends number|string>(arg: T): void { console.log(arg) }
В этом примере мы используем ключевое слово extends
, чтобы ограничить параметр типа T
только типами number
или string
. Это означает, что любой другой тип данных, переданный в качестве аргумента этой функции, приведет к ошибке компилятора.
printValue("Rabi Siddique") // Rabi Siddique printValue(24) // 24 printValue(true) // Compiler error: Argument of type 'boolean' is not assignable to parameter of type 'string | number'.
Теперь давайте посмотрим на другой пример. Предположим, у вас есть объектный интерфейс с именем Person
с двумя свойствами, name
и age
. Вы хотите создать функцию, которая принимает объект типа Person
и записывает его имя и возраст в консоль. Однако вы хотите убедиться, что функция принимает только объекты со свойствами name
и age
, а не любые другие свойства. Вот как вы можете этого добиться:
interface Person { name: string; age: number; } function logPerson<T extends Person>(person: T): void { console.log(`Name: ${person.name}, Age: ${person.age}`); }
В этом коде мы добавили ограничение к параметру типа T, используя ключевое слово extends
, за которым следует тип или интерфейс, которым мы хотим ограничить T. Здесь мы ограничили T объектами с теми же свойствами, что и интерфейс Person
. Здесь мы ограничили T
объектами с теми же свойствами, что и интерфейс Person
.
Теперь вы можете использовать эту функцию с объектом, который соответствует интерфейсу Person
, например:
const person: Person = { name: "Rabi Siddique", age: 25 }; logPerson(person); // "Name: Rabi Siddique, Age: 25"
Заключение
TypeScript Generics — это мощная функция, которая позволяет вам писать многоразовый и гибкий код. Обобщения позволяют создавать функции, классы и интерфейсы, которые могут работать с различными типами данных, без написания отдельных реализаций для каждого типа данных. Используя TypeScript Generics, вы можете сделать свой код более масштабируемым и удобным в сопровождении, обеспечивая при этом безопасность типов.
Спасибо за чтение. Я надеюсь, что этот пост будет полезен для вас. Если у вас есть дополнительные вопросы, не стесняйтесь обращаться к нам. Я всегда рад помочь.
Повышение уровня кодирования
Спасибо, что являетесь частью нашего сообщества! Перед тем, как ты уйдешь:
- 👏 Хлопайте за историю и подписывайтесь на автора 👉
- 📰 Смотрите больше контента в публикации Level Up Coding
- 💰 Бесплатный курс собеседования по программированию ⇒ Просмотреть курс
- 🔔 Подписывайтесь на нас: Twitter | ЛинкедИн | "Новостная рассылка"
🚀👉 Присоединяйтесь к коллективу талантов Level Up и найдите прекрасную работу