Предпосылки:

Базовое понимание машинописного текста.

Что такое дженерики/универсальные типы?

→ Genericsпередают некоторую дополнительную информацию, которую TypeScript не может вывести (т. е. угадать). Они позволяют продолжить работу с данными оптимальным для TypeScript способом.

→ Общий — это тип, за которым следует другой тип.

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

→ Он указывает TS, какой тип данных он будет хранить, мы можем его использовать.

ТРЮК: любой тип, написанный внутри ‹›, является универсальным.

A. Некоторые встроенные дженерики:

  1. Массив: если мы знаем, что массив будет содержать определенный тип (скажем, строку).

Здесь массив — это тип, за которым следует строка

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 больше гибкости в сочетании с безопасностью типов. Это также предотвращает некоторые невидимые логические ошибки, которые мы, как разработчики, можем случайно сделать.

Спасибо, что были до конца 🙌 . Если вам понравилась эта статья или вы узнали что-то новое, поддержите меня, нажав кнопку Поделиться ниже, чтобы привлечь больше людей, и/или подпишитесь на меня в Твиттере, чтобы увидеть другие советы, статьи и то, что я узнаю и чем делюсь.