Предпосылки:
Базовое понимание машинописного текста.
Что такое дженерики/универсальные типы?
→ 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 больше гибкости в сочетании с безопасностью типов. Это также предотвращает некоторые невидимые логические ошибки, которые мы, как разработчики, можем случайно сделать.
Спасибо, что были до конца 🙌 . Если вам понравилась эта статья или вы узнали что-то новое, поддержите меня, нажав кнопку Поделиться ниже, чтобы привлечь больше людей, и/или подпишитесь на меня в Твиттере, чтобы увидеть другие советы, статьи и то, что я узнаю и чем делюсь.