Дженерики

Основная цель разработки дженериков состоит в том, чтобы обеспечить значимые ограничения между членами, которые могут быть:

  • Экземпляр класса

  • Методы класса

  • Функциональный параметр

  • Возвращаемое значение функции

Мотивация и примеры

Вот простая реализация структуры данных «первым пришел - первым вышел», очереди в TypeScript и JavaScript.

class Queue {
  private data = [];
  push = item => this.data.push(item);
  pop = () => this.data.shift();
}

В приведенном выше коде есть проблема: он позволяет добавлять в очередь данные любого типа, конечно, когда данные выталкиваются из очереди, они также могут быть любого типа. В приведенном ниже примере кажется, что можно добавить данные типа string в очередь, но на практике это использование предполагает, что в очередь будет добавлен только тип number.

class Queue {
  private data = [];
  push = item => this.data.push(item);
  pop = () => this.data.shift();
}

const queue = new Queue();

queue.push(0);
queue.push('1'); // Oops,ошибка

// Пользователь, в недоразумении
console.log(queue.pop().toPrecision(1));
console.log(queue.pop().toPrecision(1)); // RUNTIME ERROR

Одно из решений (на самом деле это единственное решение, которое не поддерживает универсальные типы) - это создание специальных классов для этих ограничений, таких как быстрое создание очереди числовых типов:

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

Другой пример, который мы видели: reverse функция, которая теперь предоставляет ограничения на параметры функции и возвращаемые значения:

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

Вы можете вызывать универсальные параметры по желанию. Когда вы используете простые обобщенные параметры, обобщенные значения обычно представлены буквами T, U, V. Если у вас есть более одного универсального типа в вашем параметре, вы должны использовать более семантическое имя, такое как TKey и TValue (обычно префикс универсального типа T, который также называется Шаблон в С++).

Неправильные дженерики

Я видел, как разработчики используют дженерики только для взлома. Когда вы используете его, вы должны спросить себя: какие ограничения вы хотите использовать для его предоставления. Если вы не можете ответить правильно, вы можете использовать дженерики, такие как:

Здесь нет необходимости использовать дженерики, поскольку они используются только для позиции одного параметра. Возможно, лучше использовать:

Дизайн шаблона: удобный и универсальный

Рассмотрим следующую функцию:

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

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

Другой очевидный пример - функция загрузки возвращаемого значения json, которая возвращает Promise любого из переданных вами типов:

Обратите внимание, что вам все еще нужно явно сообщить тип, который вам нужен, но подпись getJSON<T> в config => Promise<T> может сократить некоторые ваши ключевые шаги (вам не нужно сообщить возвращаемый тип loadUsers, потому что его можно вытолкнуть):

Также возвращаемое значение с использованием Promise<T> как функции намного лучше, чем некоторые альтернативы, такие как Promise<any>.

Использование с axios

При нормальных обстоятельствах мы будем помещать формат данных бэкэнда в интерфейс отдельно:

Когда мы разделяем API на один модуль:

Затем мы записываем возвращаемый тип данных User, который позволяет TypeScript выводить нужный нам тип:

Last updated

Was this helpful?