2.1 Введение в дженерики
Дженерики (Generics) в TypeScript — это мощный инструмент, который позволяет создавать компоненты, способные работать с различными типами данных, обеспечивая при этом строгую типизацию. Это достигается путем определения типов-параметров, которые можно заменить конкретными типами во время использования.
Дженерики позволяют создавать универсальные компоненты, которые могут работать с различными типами данных. Это делает код более гибким и повторно используемым. Дженерики особенно полезны для создания коллекций, функций и классов, которые должны работать со множеством типов данных, сохраняя при этом типовую безопасность.
Синтаксис дженериков
Основной синтаксис дженериков в TypeScript включает использование угловых скобок (<>) для определения параметров типов.
function name<T>(arg: T): T {
}
Также возможны варианты:
- в виде типа-параметра передается тип возвращаемого значения:
function name<T>(): T { } - в виде типа-параметра передается тип параметра функции:
function name<T>(param:T) { } - или несколько параметров:
function name<T1, T2, T3>(param1:T1, param2:T2, param3:T3) { }
Пример:
"use strict";
function identity(arg) {
return arg;
}
let output1 = identity("Hello, TypeScript!");
let output2 = identity(42);
console.log(output1); // Вывод: Hello, TypeScript!
console.log(output2); // Вывод: 42
function identity<T>(arg: T): T {
return arg;
}
let output1 = identity<string>("Hello, TypeScript!");
let output2 = identity<number>(42);
console.log(output1); // Вывод: Hello, TypeScript!
console.log(output2); // Вывод: 42
Здесь функция identity принимает параметр типа T и возвращает значение того же типа. Параметр типа T заменяется конкретным типом при вызове функции.
2.2 Базовые примеры использования дженериков
Дженерики в функциях
Функции с дженериками позволяют определять типы параметров и возвращаемых значений, что делает функции более универсальными и типобезопасными.
Пример:
"use strict";
function reverseArray(items) {
return items.reverse();
}
let numbers = [1, 2, 3, 4];
let reversedNumbers = reverseArray(numbers);
console.log(reversedNumbers); // Вывод: [4, 3, 2, 1]
let strings = ["one", "two", "three"];
let reversedStrings = reverseArray(strings);
console.log(reversedStrings); // Вывод: ["three", "two", "one"]
function reverseArray<T>(items: T[]): T[] {
return items.reverse();
}
let numbers = [1, 2, 3, 4];
let reversedNumbers = reverseArray(numbers);
console.log(reversedNumbers); // Вывод: [4, 3, 2, 1]
let strings = ["one", "two", "three"];
let reversedStrings = reverseArray(strings);
console.log(reversedStrings); // Вывод: ["three", "two", "one"]
Здесь функция reverseArray принимает массив элементов типа T и возвращает массив того же типа, но в обратном порядке.
Дженерики в интерфейсах
Интерфейсы с дженериками позволяют описывать структуры данных, которые могут работать с различными типами.
Пример:
"use strict";
let pair = {
first: "Age",
second: 30
};
console.log(pair.first); // Вывод: Age
console.log(pair.second); // Вывод: 30
interface Pair<T, U> {
first: T;
second: U;
}
let pair: Pair<string, number> = {
first: "Age",
second: 30
};
console.log(pair.first); // Вывод: Age
console.log(pair.second); // Вывод: 30
Здесь интерфейс Pair принимает два параметра типа T и U, которые определяют типы его свойств first и second.
Дженерики в классах
Классы с дженериками позволяют создавать универсальные шаблоны для объектов, которые могут работать с различными типами данных.
Пример:
"use strict";
class Stack {
constructor() {
this.items = [];
}
push(item) {
this.items.push(item);
}
pop() {
return this.items.pop();
}
}
let numberStack = new Stack();
numberStack.push(10);
numberStack.push(20);
console.log(numberStack.pop()); // Вывод: 20
let stringStack = new Stack();
stringStack.push("A");
stringStack.push("B");
console.log(stringStack.pop()); // Вывод: B
class Stack<T> {
private items: T[] = [];
push(item: T): void {
this.items.push(item);
}
pop(): T | undefined {
return this.items.pop();
}
}
let numberStack = new Stack<number>();
numberStack.push(10);
numberStack.push(20);
console.log(numberStack.pop()); // Вывод: 20
let stringStack = new Stack<string>();
stringStack.push("A");
stringStack.push("B");
console.log(stringStack.pop()); // Вывод: B
Здесь класс Stack использует дженерик T для определения типа элементов, которые он может содержать. Это позволяет создавать стеки для различных типов данных, сохраняя при этом типовую безопасность.
2.3 Примеры
Дженерики широко используются в реальных проектах для создания универсальных и типобезопасных компонентов. Рассмотрим несколько примеров.
Пример 1: Функция фильтрации массива
"use strict";
function filterArray(array, predicate) {
return array.filter(predicate);
}
let numbers = [1, 2, 3, 4, 5];
let evenNumbers = filterArray(numbers, num => num % 2 === 0);
console.log(evenNumbers); // Вывод: [2, 4]
let words = ["apple", "banana", "cherry"];
let filteredWords = filterArray(words, word => word.includes("a"));
console.log(filteredWords); // Вывод: ["apple", "banana"]
function filterArray<T>(array: T[], predicate: (item: T) => boolean): T[] {
return array.filter(predicate);
}
let numbers = [1, 2, 3, 4, 5];
let evenNumbers = filterArray(numbers, num => num % 2 === 0);
console.log(evenNumbers); // Вывод: [2, 4]
let words = ["apple", "banana", "cherry"];
let filteredWords = filterArray(words, word => word.includes("a"));
console.log(filteredWords); // Вывод: ["apple", "banana"]
Здесь функция filterArray принимает массив элементов типа T и функцию-предикат, которая определяет, какие элементы должны быть включены в результирующий массив.
Пример 2: Словарь с дженериками
"use strict";
class Dictionary {
constructor() {
this.items = {};
}
setItem(key, value) {
this.items[key] = value;
}
getItem(key) {
return this.items[key];
}
}
let dictionary = new Dictionary();
dictionary.setItem("one", 1);
dictionary.setItem("two", 2);
console.log(dictionary.getItem("one")); // Вывод: 1
console.log(dictionary.getItem("three")); // Вывод: undefined
class Dictionary<TKey, TValue> {
private items: { [key: string]: TValue } = {};
setItem(key: TKey, value: TValue): void {
this.items[key as any] = value;
}
getItem(key: TKey): TValue | undefined {
return this.items[key as any];
}
}
let dictionary = new Dictionary<string, number>();
dictionary.setItem("one", 1);
dictionary.setItem("two", 2);
console.log(dictionary.getItem("one")); // Вывод: 1
console.log(dictionary.getItem("three")); // Вывод: undefined
Здесь класс Dictionary использует два параметра типа TKey и TValue для определения типов ключей и значений словаря. Это позволяет создавать словари для различных типов данных.
Преимущества использования дженериков
- Типобезопасность: дженерики обеспечивают строгую типизацию, что помогает обнаруживать ошибки на этапе компиляции.
- Гибкость и универсальность: дженерики позволяют создавать универсальные функции, классы и интерфейсы, которые могут работать с различными типами данных.
- Повторное использование кода: использование дженериков позволяет избегать дублирования кода, создавая универсальные компоненты, которые можно использовать в различных контекстах.
- Улучшенная читаемость и поддерживаемость: дженерики делают код более понятным и легко поддерживаемым, так как они четко определяют типы данных, с которыми работают компоненты.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ