JavaRush /Курсы /Модуль 2: Fullstack /Ограничения для дженериков

Ограничения для дженериков

Модуль 2: Fullstack
6 уровень , 2 лекция
Открыта

3.1 Основы ограничений для дженериков

У дженериков (Generics) есть отличный способ создавать универсальные и типобезопасные компоненты в TypeScript. Однако иногда возникает необходимость ограничить типы, которые могут быть использованы в качестве параметров типов. Это позволяет улучшить типизацию и обеспечить выполнение определенных условий для параметров типов.

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

Ограничения для дженериков позволяют указать, что тип параметра должен удовлетворять определенному условию или быть подтипом определенного типа. Это достигается путем использования ключевого слова extends при объявлении дженерика.

Синтаксис

Синтаксис для указания ограничений выглядит следующим образом:

    
      function example<T extends Constraint>(arg: T): T {
      }
    
  

Здесь T — это параметр типа, который должен удовлетворять условию Constraint.

3.2 Примеры использования ограничений для дженериков

Ограничение дженерика объектным типом

Одним из самых распространенных примеров является ограничение дженерика объектным типом. Это позволяет гарантировать, что параметр типа будет объектом с определенными свойствами.

Пример:

JavaScript
    
      "use strict";
      function logLength(arg) {
        console.log(arg.length);
      }
      logLength({ length: 10, value: "Hello" }); // Вывод: 10
      logLength([1, 2, 3]); // Вывод: 3
      // logLength(10); // Ошибка: Argument of type 'number' is not assignable to parameter of type 'Lengthwise'.
    
  
TypeScript
    
      interface Lengthwise {
        length: number;
      }

      function logLength<T extends Lengthwise>(arg: T): void {
        console.log(arg.length);
      }

      logLength({ length: 10, value: "Hello" }); // Вывод: 10
      logLength([1, 2, 3]); // Вывод: 3
      // logLength(10); // Ошибка: Argument of type 'number' is not assignable to parameter of type 'Lengthwise'.
    
  

Здесь функция logLength принимает параметр типа T, который должен быть объектом, имеющим свойство length. Попытка передать аргумент, не соответствующий этому условию, вызовет ошибку компиляции.

Ограничение дженерика базовым классом

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

JavaScript
    
      "use strict";
      class Animal {
          move() {
              console.log("Moving along...");
          }
      }
      class Dog extends Animal {
          bark() {
              console.log("Woof! Woof!");
          }
      }
      class Cat extends Animal {
          meow() {
              console.log("Meow! Meow!");
          }
      }
      function makeSound(animal) {
          animal.move();
      }
      let dog = new Dog();
      let cat = new Cat();
      makeSound(dog); // Вывод: Moving along...
      makeSound(cat); // Вывод: Moving along...
    
  
TypeScript
    
      class Animal {
        move(): void {
            console.log("Moving along...");
        }
      }

      class Dog extends Animal {
        bark(): void {
            console.log("Woof! Woof!");
        }
      }

      class Cat extends Animal {
        meow(): void {
            console.log("Meow! Meow!");
        }
      }

      function makeSound<T extends Animal>(animal: T): void {
        animal.move();
      }

      let dog = new Dog();
      let cat = new Cat();

      makeSound(dog); // Вывод: Moving along...
      makeSound(cat); // Вывод: Moving along...
    
  

Здесь функция makeSound принимает параметр типа T, который должен быть подклассом Animal. Это позволяет вызывать метод move для любого объекта, передаваемого в функцию makeSound.

Ограничения для нескольких параметров типов

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

Пример:

JavaScript
    
      "use strict";
      function merge(obj1, obj2) {
          return Object.assign(Object.assign({}, obj1), obj2);
      }
      const person = { name: "Alice" };
      const details = { age: 30, job: "Developer" };
      const mergedObject = merge(person, details);
      console.log(mergedObject); // Вывод: { name: 'Alice', age: 30, job: 'Developer' }
    
  
TypeScript
    
      function merge<T extends object, U extends object>(obj1: T, obj2: U): T & U {
        return { ...obj1, ...obj2 };
      }

      const person = { name: "Alice" };
      const details = { age: 30, job: "Developer" };

      const mergedObject = merge(person, details);
      console.log(mergedObject); // Вывод: { name: 'Alice', age: 30, job: 'Developer' }
    
  

Здесь функция merge принимает два параметра типов T и U, которые должны быть объектами. Она объединяет два объекта и возвращает новый объект, содержащий свойства обоих исходных объектов.

Ограничение по типу ключа объекта

Иногда необходимо ограничить дженерик так, чтобы он соответствовал типам ключей определенного объекта. Это можно сделать с помощью оператора keyof. Подробнее об операторе keyof вы узнаете через пару лекций.

Пример:

JavaScript
    
      "use strict";
      function getProperty(obj, key) {
          return obj[key];
      }
      const car = { make: "Toyota", model: "Camry", year: 2020 };
      let make = getProperty(car, "make");
      let year = getProperty(car, "year");
      console.log(make); // Вывод: Toyota
      console.log(year); // Вывод: 2020
      // let invalid = getProperty(car, "color"); // Ошибка: Argument of type '"color"' is not assignable to parameter of type '"make" | "model" | "year"'.
    
  
TypeScript
    
      function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
        return obj[key];
      }

      const car = { make: "Toyota", model: "Camry", year: 2020 };

      let make = getProperty(car, "make");
      let year = getProperty(car, "year");

      console.log(make); // Вывод: Toyota
      console.log(year); // Вывод: 2020
      // let invalid = getProperty(car, "color"); // Ошибка: Argument of type '"color"' is not assignable to parameter of type '"make" | "model" | "year"'.
    
  

Здесь функция getProperty принимает объект obj типа T и ключ key типа K, который должен быть ключом объекта T. Это гарантирует, что переданный ключ будет допустимым для данного объекта.

3.3 Реальные примеры

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

Пример 1: Функция копирования объектов

JavaScript
    
      "use strict";
      function copy(obj) {
          return Object.assign({}, obj);
      }
      const original = { name: "Alice", age: 30 };
      const copyObj = copy(original);
      console.log(copyObj); // Вывод: { name: 'Alice', age: 30 }
    
  
TypeScript
    
      function copy<T extends object>(obj: T): T {
        return { ...obj };
      }

      const original = { name: "Alice", age: 30 };
      const copyObj = copy(original);

      console.log(copyObj); // Вывод: { name: 'Alice', age: 30 }
    
  

В этом примере функция copy принимает объект и возвращает его копию. Ограничение T extends object гарантирует, что параметр типа будет объектом.

Пример 2: Поиск элемента в массиве объектов

JavaScript
    
      "use strict";
      function findById(items, id) {
          return items.find(item => item.id === id);
      }
      const users = [
          { id: 1, name: "Alice" },
          { id: 2, name: "Bob" },
          { id: 3, name: "Charlie" }
      ];
      const user = findById(users, 2);
      console.log(user); // Вывод: { id: 2, name: 'Bob' }
    
  
TypeScript
    
      interface Identifiable {
        id: number;
      }

      function findById<T extends Identifiable>(items: T[], id: number): T | undefined {
        return items.find(item => item.id === id);
      }

      const users = [
        { id: 1, name: "Alice" },
        { id: 2, name: "Bob" },
        { id: 3, name: "Charlie" }
      ];

      const user = findById(users, 2);
      console.log(user); // Вывод: { id: 2, name: 'Bob' }
    
  

Здесь функция findById принимает массив объектов типа T, который должен иметь свойство id. Это позволяет искать объекты по их идентификатору.

Преимущества использования ограничений для дженериков

  1. Улучшенная типизация: ограничения позволяют точно определить типы, которые можно использовать в качестве параметров типов, что улучшает типовую безопасность кода.
  2. Гибкость и универсальность: ограничения делают дженерики более гибкими и универсальными, позволяя использовать их в широком диапазоне сценариев.
  3. Повышение надежности кода: ограничения помогают избежать ошибок, связанных с использованием неподходящих типов, что повышает надежность кода.
  4. Улучшенная читаемость и поддерживаемость: явные ограничения для дженериков делают код более понятным и легким для поддержки, так как сразу видно, какие типы допустимы.
3
Задача
Модуль 2: Fullstack, 6 уровень, 2 лекция
Недоступна
Подсчет длины элемента
Подсчет длины элемента
3
Задача
Модуль 2: Fullstack, 6 уровень, 2 лекция
Недоступна
Объединение объектов
Объединение объектов
3
Задача
Модуль 2: Fullstack, 6 уровень, 2 лекция
Недоступна
Поиск по ключу
Поиск по ключу
3
Задача
Модуль 2: Fullstack, 6 уровень, 2 лекция
Недоступна
Поиск элемента по идентификатору
Поиск элемента по идентификатору
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ