Если вы только начинаете работать с 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, что означает, что оно может содержать любое значение любого типа.

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

const stringBox: Box<string> = { value: 'Rabi Siddique' };
const numberBox: Box<number> = { value: 27 };

В этом коде мы создаем два экземпляра Boxinterface, один для строк и один для чисел.

Вы также можете использовать параметр универсального типа для создания интерфейсов с несколькими свойствами одного типа. Вот пример:

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, вы можете сделать свой код более масштабируемым и удобным в сопровождении, обеспечивая при этом безопасность типов.

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

Подключаемся:
LinkedIn
Twitter

Повышение уровня кодирования

Спасибо, что являетесь частью нашего сообщества! Перед тем, как ты уйдешь:

  • 👏 Хлопайте за историю и подписывайтесь на автора 👉
  • 📰 Смотрите больше контента в публикации Level Up Coding
  • 💰 Бесплатный курс собеседования по программированию ⇒ Просмотреть курс
  • 🔔 Подписывайтесь на нас: Twitter | ЛинкедИн | "Новостная рассылка"

🚀👉 Присоединяйтесь к коллективу талантов Level Up и найдите прекрасную работу