Базовые дженерики в TypeScript
Дженерики позволяют создавать «переменные типа» — заполнители для типа, который подставляется позже. Их можно использовать в классах, функциях и псевдонимах типов, чтобы не указывать в них точные типы, с которыми они работают.
С дженериками проще писать код, который можно переиспользовать со многими разными типами.
Функции
Обобщённые функции более универсальны: они работают с любым типом и при этом точно описывают типы аргументов и возвращаемого значения.
Примечание: TypeScript также может вывести параметры типа из переданных аргументов, поэтому часто их не нужно писать явно.
Пример
S и T — это переменные типа. Вызов подставляет вместо них string и number, поэтому результат имеет тип [string, number].
createPair<S, T>, которая принимает v1: S и v2: T и возвращает кортеж [S, T], затем выведите createPair<string, number>('hello', 42), чтобы результатом был массив [ 'hello', 42 ].
Вывод: программа на TypeScript и захваченный вывод
function createPair<S, T>(v1: S, v2: T): [S, T] {
return [v1, v2];
}
console.log(createPair<string, number>('hello', 42)); // ['hello', 42]
Классы
Дженерики также делают универсальным класс, как встроенный Map, который работает с любыми типами ключей и значений.
Примечание: TypeScript может вывести параметр типа класса, когда тот используется в параметре конструктора.
Пример
NamedValue<number> делает T типом number, поэтому setValue принимает только числа.
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 и захваченный вывод
class NamedValue<T> {
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<number>('myNumber');
value.setValue(10);
console.log(value.toString()); // myNumber: 10
Псевдонимы типов
Дженерики в псевдонимах типов позволяют создавать типы, которые можно переиспользовать со многими разными внутренними типами.
Примечание: То же самое работает и для интерфейсов, с синтаксисом interface Wrapped<T> {
Синтаксис обобщённого псевдонима типа
Wrapped<T> — это тип объекта, у которого value имеет тот тип, который вы передали, например number.
type Wrapped<T> = { value: T };
const wrappedValue: Wrapped<number> = { value: 10 };
Значение по умолчанию
У параметра типа может быть значение по умолчанию. Оно используется, когда тип не указан и не может быть выведен.
Параметр типа по умолчанию
T = string означает, что new NamedValue('myNumber') без аргумента типа использует string в качестве T.
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 можно передать только строки и числа, поэтому значения можно безопасно вывести на печать.
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];
}