Перейти к основному содержимому

Базовые дженерики в TypeScript

Дженерики позволяют создавать «переменные типа» — заполнители для типа, который подставляется позже. Их можно использовать в классах, функциях и псевдонимах типов, чтобы не указывать в них точные типы, с которыми они работают.

С дженериками проще писать код, который можно переиспользовать со многими разными типами.


Функции

Обобщённые функции более универсальны: они работают с любым типом и при этом точно описывают типы аргументов и возвращаемого значения.

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

Пример

S и T — это переменные типа. Вызов подставляет вместо них string и number, поэтому результат имеет тип [string, number].

Prompt
Показать обобщённую функцию с двумя параметрами типа. Напишите функцию createPair<S, T>, которая принимает v1: S и v2: T и возвращает кортеж [S, T], затем выведите createPair<string, number>('hello', 42), чтобы результатом был массив [ 'hello', 42 ].
Вывод: программа на TypeScript и захваченный вывод

Классы

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

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

Пример

NamedValue<number> делает T типом number, поэтому setValue принимает только числа.

Prompt
Показать обобщённый класс, тип хранимого значения которого задаётся параметром типа. Напишите класс NamedValue<T> с приватным полем _value: T | undefined, конструктором, принимающим private name: string, методами setValue(value: T), getValue(): T | undefined и toString(), возвращающим "name: value"; создайте new NamedValue<number>('myNumber'), установите значение 10 и выведите toString(), напечатав "myNumber: 10".
Вывод: программа на TypeScript и захваченный вывод

Псевдонимы типов

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

Примечание: То же самое работает и для интерфейсов, с синтаксисом interface Wrapped<T> {

Синтаксис обобщённого псевдонима типа

Wrapped<T> — это тип объекта, у которого value имеет тот тип, который вы передали, например number.

script.ts
type Wrapped<T> = { value: T };

const wrappedValue: Wrapped<number> = { value: 10 };

Значение по умолчанию

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

Параметр типа по умолчанию

T = string означает, что new NamedValue('myNumber') без аргумента типа использует string в качестве T.

script.ts
class NamedValue<T = string> {
  private _value: T | undefined;

  constructor(private name: string) {}

  public setValue(value: T) {
    this._value = value;
  }

  public getValue(): T | undefined {
    return this._value;
  }

  public toString(): string {
    return `${this.name}: ${this._value}`;
  }
}

let value = new NamedValue('myNumber');
value.setValue('myValue');
console.log(value.toString()); // myNumber: myValue

Extends

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

Ограничение позволяет опираться на более конкретный тип при использовании переменной типа.

Примечание: Ограничение можно сочетать со значением по умолчанию.

Ограничение параметра типа

S extends string | number означает, что для S можно передать только строки и числа, поэтому значения можно безопасно вывести на печать.

script.ts
function createLoggedPair<S extends string | number, T extends string | number>(v1: S, v2: T): [S, T] {
  console.log(`creating pair: v1='${v1}', v2='${v2}'`);
  return [v1, v2];
}