
Предпосылки:
Базовое понимание машинописного текста.
Что такое дженерики/универсальные типы?
→ Genericsпередают некоторую дополнительную информацию, которую TypeScript не может вывести (т. е. угадать). Они позволяют продолжить работу с данными оптимальным для TypeScript способом.
→ Общий — это тип, за которым следует другой тип.
Например, есть переменная типа массив. Теперь массив может содержать данные любого типа, т. е. строку, число и т. д.
→ Он указывает TS, какой тип данных он будет хранить, мы можем его использовать.
ТРЮК: любой тип, написанный внутри ‹›, является универсальным.
A. Некоторые встроенные дженерики:
- Массив: если мы знаем, что массив будет содержать определенный тип (скажем, строку).
Здесь массив — это тип, за которым следует строка
const arr: Array <string> = [];
В этом случае существует старый способ определения, который также в основном используется:
const arr: string[] = [];
2. Обещания: если мы знаем, что обещание вернет строку.
Здесь обещание — это тип, за которым следует строка (т. е. это обещание вернет строку)
const promise: Promise<string> = new Promise((resolve, reject) => {
setTimeout(() => {
try {
resolve("Hola, data succeded.");
} catch (e) {
reject("Something went wrong " + e);
}
}, 2000);
});
promise.then(data => {
data.split(' ');
});
→ Поскольку typescript теперь знает, что результатом будет строка, в редакторе также появится функция автозаполнения строки.


→ Typescript не разрешит компиляцию, если вы случайно вернете другой тип. Следовательно, дженерики — очень мощная функция TypeScript.

B. Пользовательские универсальные функции
→ Давайте рассмотрим пример того, почему нам нужна общая функция
Пример 1: Предположим, у нас есть функция, которая объединяет два объекта.
function merge(obj1: object, obj2: object) {
return Object.assign(obj1, obj2);
}
const mergedResult = merge({name: 'Amir'}, {gender: 'male'});
console.log(mergedResult); // O/P: get merged obj i.e. {name: 'Amir', gender: 'male'}
→ Вышеупомянутая функция будет работать нормально, и мы получим объединенный объект.
→ Проблема: когда TypeScript пытается получить доступ к свойству объединенного объекта, не удается его вывести (т.е. угадать) и >выдать ошибку.
TS выводит только объекты, а не полные объекты, поэтому компилятор TS не будет работать успешно.

→Спасибо Generics за то, что избавили нас от таких проблем.
→ В стандартном принятом синтаксисе используются переменные T и U. Мы также можем использовать любое имя по нашему выбору.
Пример 2: замените тип объекта на T и U, а также напишите в функции как дженерики.
function merge <T, U> (obj1: T, obj2: U) {
return Object.assign(obj1, obj2);
}
const mergedResult = merge({name: 'Amir'}, {gender: 'male'});
// console.log(mergedResult); // get merged obj i.e. {name: 'Amir', gender: 'male'}
console.log(mergedResult.name);
→ Теперь TS сможет вывести свойства объединенных объектов и будет компилироваться без ошибок.

→ Таким образом, ключевая идея заключается в том, что при написании дженериков это дает пересечение объектов, следовательно, свойство доступно.
T и U просто означают, что это может быть любой тип, и typescript это понимает.
→ Если мы хотим быть более конкретными, мы можем указать T и U во время вызова
Eg3:
function merge <T, U> (obj1: T, obj2: U) {
return Object.assign(obj1, obj2);
}
// const mergedResult = merge({name: 'Amir'}, {gender: 'male'});
const mergedResult = merge<string, number>('test data', 5); // better TS infer it
// console.log(mergedResult.name);
Хорошая практика: мы не должны указывать тип явно и позволять TS делать вывод о типе o/p.
C. Работа с ограничениями:
→ Это означает разрешить только определенные типы, иначе возникнут ошибки.
Например. В том же примере мы передаем один объект и одно число. Эта функция должна была объединить два объекта. TS молча игнорирует второй параметр. С точки зрения разработки есть ошибка.
Eg4.
function merge <T, U> (obj1: T, obj2: U) {
return Object.assign(obj1, obj2);
}
const mergedResult = merge({name: 'Amir'}, 5);
console.log(mergedResult); // O/P: {name: 'Amir'}, TS silently ignores number

→ Чтобы уберечь разработчиков от этого типа ошибок, очень полезны ограничения.
→ Ограничение записывается с помощью ключевого слова extends, за которым следует ожидаемый тип
Eg.
function merge <T extends object, U extends object> (obj1: T, obj2: U) {
return Object.assign(obj1, obj2);
}
const mergedResult = merge({name: 'Amir'}, 5);
console.log(mergedResult); // TS will force to pass obj


Например, 2.1. Давайте рассмотрим другой пример с интерфейсом в качестве типа функции.
→ Интерфейс — это концепция, представленная в TS. Это контракт, которому должна следовать функция или класс.
interface Lengthy {
length: number
}
function countAndDescribe <T extends Lengthy> (element: T) {
let descriptionText = 'Value not found.';
if(element.length === 1)
descriptionText = 'Got 1 element';
else if(element.length > 1)
descriptionText = `Got ${element.length} values`;
return [element, descriptionText];
}
console.log(countAndDescribe('Hi there')); // String accepted
console.log(countAndDescribe(["Sports", "Cooking"]));
console.log(countAndDescribe([])); // All types having length property is allowed

→ Здесь дженерики ожидают тип, соответствующий требованиям интерфейса. Интерфейс допускает любой тип, внутри которого есть свойство length.
→ Строка и массивы имеют свойство длины. TS будет успешно скомпилирован. Теперь мы видим, что универсальные шаблоны надежны.
→ Если мы попытаемся передать число (которое не имеет свойства длины)

→ Итак, идея здесь в том, что дженерикам все равно, какой тип данных передается (т. Е. Массив или строка). Он должен иметь свойство длины
D. ключ в ограничениях:
→ Предположим, мы пытаемся получить доступ к свойству несуществующего объекта. TypeScript не выдает ошибок и возвращает неопределенное значение.
→ Чтобы исправить это во время разработки. используется ограничение key-of.
Пример 1: без дженериков:
function extractAndConvert(obj: object, key: string) {
return 'Value is ' + obj[key];
}
console.log(extractAndConvert({}, 'name')); // O/P: Value is undefined
Пример 2: с ключом:
function extractAndConvert<T extends object,U extends keyof T>(obj: T, key: U) {
return 'Value is ' + obj[key];
}
console.log(extractAndConvert({}, 'name'));
→ TS теперь будет выдавать ошибку компиляции, чтобы либо добавить указанное свойство в объект, либо удалить такой код.


E. Общие типы с классами:
Давайте посмотрим пример:

→ ТС жалуется на возвращаемый тип элементов. Для этого мы будем использовать дженерики в классах.
Eg3
class DataStorage<T> {
private data: T[] = [];
addItem(item: T) {
this.data.push(item);
}
removeItem(item: T) {
this.data.splice(this.data.indexOf(item), 1);
}
getItem() {
return this.data;
}
}
const textStorage = new DataStorage<string>();
textStorage.addItem("Amir"); // Adding data
textStorage.addItem("Nasir");
textStorage.addItem("Ronith");
textStorage.removeItem("Amir"); // Removing data
console.log(textStorage.getItem()); // Getting data


→ Приведенный выше код работает гладко для данных примитивного типа (например, чисел, строк и т. д.). Возникла проблема с объектами
→ Мы всегда должны устанавливать объект в переменной, а затем использовать его везде, поскольку объекты относятся к ссылочному типу.
→ Мы также можем гибко использовать ограничения
class DataStorage<T extends string | number | boolean> {
// class data
}
ПРИМЕЧАНИЕ. Generic позволяет выбрать только пользователя, которого вы можете использовать в любом из вышеперечисленных типов, т. е. в виде строки, числа или логического значения. Один раз вставьте данные в массив определенного типа. Это не позволит добавить другой тип.
ПРИМЕЧАНИЕ:
Конкретный тип разрешен
или любой тип, который имеет допустимую длину (как мы видели в предыдущем, например.)
F. Некоторая полезная общая функция:
→ Мы уже видели некоторые служебные функции (т. е. встроенные функции) в начале этого блога. то есть массив и обещания. Давайте посмотрим еще несколько
3. Частично:
Пример 1. Предположим, у нас есть функция, тип возвращаемого значения которой — интерфейс. Ниже код пройдет компилятор TS:
→ Функция ожидает, что возвращаемый тип функции должен иметь все упомянутые типы интерфейса, которые работают нормально, когда мы вернулись сразу.
interface CourseGoal {
title: string;
description: string;
completeUntil: Date;
}
function courseGoal(
title: string,
desscription: string,
date: Date
): CourseGoal {
return { title: title, description: desscription, completeUntil: date }; when we return in obj created. in fly will work fine
}
Пример 2. Предположим, есть сценарий, в котором вы не хотите возвращать объект во время полета и добавляете шаг за шагом, а затем возвращаетесь. После выполнения некоторых проверок.
interface CourseGoal {
title: string;
description: string;
completeUntil: Date;
}
function courseGoal(
title: string,
description: string,
date: Date
): CourseGoal { // After parameter - : means return type of function
let courseGoal = {};
// some logic to be handled first (say business requirement).. eg validation
courseGoal.title = title; // TS will throw Error
courseGoal.description = description;
courseGoal.date = date;
}

→ TS выдаст ошибку, потому что ожидает, что все свойства будут там одновременно.
→ С помощью частичных мы можем указать TS игнорировать проверки типов сейчас. После добавления всех свойств мы изменим тип на ожидаемый тип, то есть на CourseGoal.
Пример 2.2: с частями:
interface CourseGoal {
title: string;
description: string;
completeUntil: Date;
}
function courseGoal(
title: string,
description: string,
date: Date
): CourseGoal {
let courseGoal: Partial<CourseGoal> = {}; // Added partial - TS will ignore type check
// some logic.. eg validation
courseGoal.title = title;
courseGoal.description = description;
courseGoal.completeUntil = date;
return courseGoal as CourseGoal; // Reverted back to original type
}

4. Универсальные шаблоны ReadyOnly:
→ Предположим, мы хотим заблокировать массив, чтобы в будущем код нельзя было изменить, мы можем использовать эту служебную функцию.
Например: Только для чтения:
const names: Readonly<string[]> = [“Amir”, “Nasir”, “Ronith”]; names.push(“Farhin”); console.log(“names”, names);

Разница между дженериками и союзами:
→ Часто возникает путаница между использованием дженериков и союзов.
Объединение. Когда мы определяем объединение — это смесь того, что мы вставляем — скажем, строки, числа или логического значения в массив или объект.

Общие: это позволит пользователю выбрать только любой из вышеперечисленных типов, т.е. строка, число или логическое значение. Однократно поместите данные в массив определенного типа. Это не позволит добавить другой тип.
ТРЮК:
Типы объединения полезны, когда вы хотите выбрать сочетание типов.
Общие полезны, когда вы хотите заблокировать определенные типы, т. е. всю строку или объект или любой тип, имеющий длину и т. д.
Заключительные мысли:
Generic дает TypesScript больше гибкости в сочетании с безопасностью типов. Это также предотвращает некоторые невидимые логические ошибки, которые мы, как разработчики, можем случайно сделать.
Спасибо, что были до конца 🙌 . Если вам понравилась эта статья или вы узнали что-то новое, поддержите меня, нажав кнопку Поделиться ниже, чтобы привлечь больше людей, и/или подпишитесь на меня в Твиттере, чтобы увидеть другие советы, статьи и то, что я узнаю и чем делюсь.