Generic typescript react это

Реакт Компоненты-дженерики

Однажды мне понадобилось написать гибкий на типизацию компонент в React. Мне нужно было, чтобы в зависимости от одного пропса в виде массива элементов, менялся другой пропс, а точнее аргумент рендер функции, которую я тоже должен передавать в качестве пропса. В обычных функциях тс, я бы подобную проблему решил через дженерики, и поэтому у меня появилась идея написать компонент дженерик.

Базовые понятия

Прежде чем написать свой первый компонент-дженерик, нужно понять и научиться писать обычные Тайпскрипт Дженерики.

Для чего вообще нужны дженерики? Они нужны для того, чтобы мы могли использовать наши конструкции кода не только с заранее заданными типами, но и иметь вариативность.

Проще написать, к сожалению, не получилось, поэтому предлагаю разобраться на примере.

Давайте представим, что у нас есть какая-то функция, которая имитирует запрос к серверу и отдает нам значение в виде обертки над нашим аргументом.

function request(arg: string) < return < status: 200, data: arg >>

По коду выше наша функция будет иметь тип

function request(arg: string):

Все, что написано выше — это, конечно, здорово, но давайте представим, что мы хотим работать с этой фукнцией не только с аргументом строкой.

interface Response < status: number; data: unknown; >function request(arg: unknown): Response < return < status: 200, data: arg >>
function request(arg: number) < return < status: 200, data: arg >>

Что нам даст использование дженерика

function request(arg: T) < return < status: 200, data: arg >> // Если мы вызовем request с числом request(100) // То request будет иметь тип function request(arg: number): < status: number; data: number; >// А если со строкой request('100'); // То function request(arg: string):

Если хотите более подробно познакомиться с дженериками, можете посмотреть документацию

Как пишутся компоненты-дженерики

class CustomComponent extends React.Component < // . >
function CustomComponent (props: React.PropsWithChildren): React.ReactElement< // . >
const CustomComponent = (props: React.PropsWithChildren: React.ReactElement => < // . >

Какие задачи решают

Давайте рассмотрим полезность на простом примере

Что же нам даст использование этого компонента на деле

const AnotherCustomComponent: React.FC = () => < const data = ['text', 'text', 'text']; return ( 
onClick= />
) >

в примере выше CustomComponent будет ожидать в onClick функцию (element: string) => void так как в data был передан массив строк

Теперь давайте рассмотрим пример, где в качестве data у нас будет не массив примитивов, а массив сущностей

class User < constructor( public name: string, public lastName: string )<>get fullName() < return `$$` > > const AnotherCustomComponent: React.FC = () => < const data = [ new User('Джон', 'Сина'), new User('Дуэйн', 'Джонсон'), new User('Дейв', 'Батиста'), ]; return ( 
onClick= alert(user.fullName)> />
) >

теперь функция onClick будет иметь тип (element: User) => void

Давайте рассмотрим немного другой пример

Так как мы в data передавали массив юзеров children будет иметь тип (element: User) => React.ReactElement

По аналогии с дженериками-функциями в дженерики-компоненты можно явно определить нужный тип, с которым мы бы хотели работать, но такой синтаксис я редко использую

const AnotherCustomComponent: React.FC = () => < const data = [ new User('Джон', 'Сина'), new User('Дуэйн', 'Джонсон'), new User('Дейв', 'Батиста'), ]; return ( 
data= onClick= alert(user)> > < // тут тип как в onClick тоже высчитается (user) =>< return
Пользователь: > >
) >
const AnotherCustomComponent: React.FC = () => < const data = [ new User('Джон', 'Сина'), new User('Дуэйн', 'Джонсон'), new User('Дейв', 'Батиста'), ]; return ( 
data= onClick= alert(user)> > < (user) =>< return
Пользователь: > >
) >

Полезность на личном опыте

Реальные примеры из жизни не стал прикладывать, так как мои компоненты довольно жирненькие, поэтому решил показать полезность на псевдо примерах, но в качестве бонуса решил словами описать последнюю проблему, которую решил через компонент-дженерик.

Мне нужно было сделать списки для товаров и категорий, которые можно было драг-н-дропать, для изменения порядка отображения, и функционал у этих двух списков идентичен, кроме отображения. Я написал один компонент, который мог работать с драг-н-дропом, у него был пропс children который являлся рендер функцией для элемента, что мне и позволило менять отображение списка при одинаковом функционале.

Заключение

В заключении хотелось бы сказать, что компоненты-дженерики хоть и довольно нишевы, но бывают очень полезны. Надеюсь моя статья будет очень полезна и поможет вам написать свой первый компонент-дженерик.

Источник

Дженерики в TypeScript: разбираемся вместе

Наверное, только матёрые разработчики Java или других строго типизированных языков не хлопают глазами, увидев дженерик в TypeScript. Его синтаксис коренным образом отличается от всего того, что мы привыкли видеть в JavaScript, поэтому так непросто сходу догадаться, что он вообще делает.

Я бы хотел показать вам, что на самом деле всё гораздо проще, чем кажется. Я докажу, что если вы способны реализовать на JavaScript функцию с аргументами, то вы сможете использовать дженерики без лишних усилий. Поехали!

Дженерики в TypeScript

В документации TypeScript приводится следующее определение: «дженерики — это возможность создавать компоненты, работающие не только с одним, а с несколькими типами данных».

Здорово! Значит, основная идея состоит в том, что дженерики позволяют нам создавать некие повторно используемые компоненты, работающие с различными типами передаваемых им данных. Но как это возможно? Вот что я думаю.

Дженерики и типы соотносятся друг с другом, как значения и аргументы функции. Это такой способ сообщить компонентам (функциям, классам или интерфейсам), какой тип необходимо использовать при их вызове так же, как во время вызова мы сообщаем функции, какие значения использовать в качестве аргументов.

Лучше всего разобрать это на примере дженерика тождественной функции. Тождественная функция — это функция, возвращающая значение переданного в неё аргумента. В JavaScript она будет выглядеть следующим образом:

function identity (value) < return value; >console.log(identity(1)) // 1

Сделаем так, чтобы она работала с числами:

function identity (value: Number) : Number < return value; >console.log(identity(1)) // 1

Отлично, мы добавили в определение тождественной функции тип, но хотелось бы, чтобы она была более гибкой и срабатывала для значений любого типа, а не только для чисел. Именно для этого и нужны дженерики. Они позволяют функции принимать значения любого типа данных на входе и, в зависимости от них, преобразовывать саму функцию.

function identity (value: T) : T < return value; >console.log(identity(1)) // 1

Ох уж этот странный синтаксис ! Отставить панику. Мы всего лишь передаём тип, который хотим использовать для конкретного вызова функции.

Посмотрите на картинку выше. Когда вы вызываете identity(1) , тип Number — это такой же аргумент, как и 1. Он подставляется везде вместо T . Функция может принимать несколько типов аналогично тому, как она принимает несколько аргументов.

Посмотрите на вызов функции. Теперь-то синтаксис дженериков не должен вас пугать. T и U — это просто имена переменных, которые вы назначаете сами. При вызове функции вместо них указываются типы, с которыми будет работать данная функция.

Альтернативная версия понимания концепции дженериков состоит в том, что они преобразуют функцию в зависимости от указанного типа данных. На анимации ниже показано, как меняется запись функции и возвращаемый результат при изменении типа.

Как можно видеть, функция принимает любой тип, что позволяет создавать повторно используемые компоненты различных типов, как и было обещано в документации.

Обратите особое внимание на второй вызов console.log на анимации выше — в него не передаётся тип. В этом случае TypeScript попытается вычислить тип по переданным данным.

Обобщённые классы и интерфейсы

Вам уже известно, что дженерики — это всего лишь способ передать типы в компонент. Только что вы видели, как они работают с функциями, и у меня хорошие новости: с классами и интерфейсами они работают точно таким же образом. В этом случае указание типов следует после имени интерфейса или класса.

Посмотрите на пример и попробуйте разобраться сами. Надеюсь, у вас получилось.

interface GenericInterface  < value: U getIdentity: () =>U > class IdentityClass implements GenericInterface  < value: T constructor(value: T) < this.value = value >getIdentity () : T < return this.value >> const myNumberClass = new IdentityClass(1) console.log(myNumberClass.getIdentity()) // 1 const myStringClass = new IdentityClass("Hello!") console.log(myStringClass.getIdentity()) // Hello!

Если код сразу не понятен, попробуйте отследить значения type сверху вниз вплоть до вызовов функции. Порядок действий следующий:

  1. Создаётся новый экземпляр класса IdentityClass , и в него передаются тип Number и значение 1 .
  2. В классе значению T присваивается тип Number .
  3. IdentityClass реализует GenericInterface , и нам известно, что T — это Number , а такая запись эквивалентна записи GenericInterface .
  4. В GenericInterface дженерик U становится Number . В данном примере я намеренно использовал разные имена переменных, чтобы показать, что значение типа переходит вверх по цепочке, а имя переменной не имеет никакого значения.

Реальные случаи использования: выходим за рамки примитивных типов

Во всех приведённых выше вставках кода были использованы примитивные типы вроде Number и string . Для примеров самое то, но на практике вы вряд ли станете использовать дженерики для примитивных типов. Дженерики будут по-настоящему полезны при работе с произвольными типами или классами, формирующими дерево наследования.

Рассмотрим классический пример наследования. Допустим, у нас есть класс Car , являющийся основой классов Truck и Vespa . Пропишем служебную функцию washCar , принимающую обобщённый экземпляр Car и возвращающую его же.

class Car < label: string = 'Generic Car' numWheels: Number = 4 horn() < return "beep beep!" >> class Truck extends Car < label = 'Truck' numWheels = 18 >class Vespa extends Car < label = 'Vespa' numWheels = 2 >function washCar (car: T) : T < console.log(`Received a $in the car wash.`) console.log(`Cleaning all $ tires.`) console.log('Beeping horn -', car.horn()) console.log('Returning your car now') return car > const myVespa = new Vespa() washCar(myVespa) const myTruck = new Truck() washCar(myTruck)

Сообщая функции washCar , что T extends Car , мы обозначаем, какие функции и свойства можем использовать внутри этой функции. Дженерик также позволяет возвращать данные указанного типа вместо обычного Car .

Результатом выполнения данного кода будет:

Received a Vespa in the car wash. Cleaning all 2 tires. Beeping horn - beep beep! Returning your car now Received a Truck in the car wash. Cleaning all 18 tires. Beeping horn - beep beep! Returning your car now

Подведем итоги

Надеюсь, я помог вам разобраться с дженериками. Запомните, всё, что вам нужно сделать, — это всего лишь передать значение type в функцию 🙂

Если хотите ещё почитать про дженерики, я прикрепил далее пару ссылок.

Что почитать:

Источник

Читайте также:  Show image in html by url
Оцените статью