058 Вводное видео
Дженерики - это определённая функция с плейсходлдером, в которую мы можем подставить определённый тип. Он позволяет сохранить типовую динамичность функции (как в нативном JS) и сохраняет type safety в проектах. Мы используем дженерики для эффективного переиспользования кода.
059 Пример встроенных generic
Встроенные дженерики в ТС отчётливо показывают нам, какие типы должны возвращать и хранить объекты. Если создать тот же массив чисел мы можем через number[]
, то уже определить возвращаемый из промиса тип у нас просто так не получится.
Чтобы реализовать типизацию у промиса, можно прописать Promise<возвращаемый_тип>
И вот пример создания записной книжки через объект (а не интерфейс), которая принимает в себя ключ(строку) - значение(булеан).
060 Пишем функцию с generic
Дженерики могут нам пригодиться ровно в тех случаях, когда нам нужно реализовать функцию, которая в себя может принять и обработать любой тип данных (либо какие-то определённые) Конкретно в этом случае, мы передаём пока один тип значения - дженерик тут не нужен
Но если у нас появится потребность добавить ещё один тип в качестве передаваемого значения, то нам нужно будет реализовывать сужение под каждый из принимаемых типов
И вот на помощь нам приходят дженерики. Они позволяют сделать некую обобщённую функцию, которая позволит работать с любым передаваемым типом
Главный признак таких функций - они могут работать с any
И теперь мы видим, что наша функция универсальна для любых типов данных
Так же хочется отметить, что мы можем валидировать типы данных, получаемых из дженериков Нам нужно получить строку из дженерика - укажем этот тип
Далее нам нужно создать функцию, которая будет принимать в себя массив и возвращать половину от него.
Создадим обобщённый дженерик и сталкиваемся с проблемой, что компилятор нам говорит - не у всех передаваемых значений будет значение длины (которое нам нужно от массива)
Но тут наша функция не может работать с any
данными
А вот уже в данном случае, мы сможем обратиться к свойству массива, так как data
имеет тип: Массив<любой_тип_данных>
. И возвращаем тут тоже массив любого типа данных
Таким образом мы можем записать стрелочную функцию
Так же дженерикам можно задать значение по умолчанию
061 Упражнение - Функция преобразования в строку
Нам нужно реализовать функцию toString
, которая будет выводить либо строку, либо undefined
(если значение не передано)
062 Использование в типах
Таким образом мы можем присвоить функцию в другую переменную, описав её тип дженериком
Дженерики можно использовать не только в функциях, но и в рамках описания любого типа объектов в рамках интерфейсов или типов (а так же классов)
063 Ограничение generic
В ТС есть возможность ограничить получаемые значения дженерика через extends этого дженерика типами, классами, интерфейсами или тайпами
И тут мы видим пример, когда мы ограничиваем передаваемые типы в дженерик-функцию, тип которой экстендится от класса Vehicle
Так же extends
работает и с интерфейсами
Так же дженерики можно экстендить union
-типами
Но так же мы можем добавлять в код сразу несколько дженериков
Например, тут T
дженерик, ограниченный юнион-типом и Y
дженерик без ограничения
064 Упражнение - Функция сортировки id
Нужно написать функцию, которая будет сортировать любые объекты с id
по возрастанию и убыванию.
Конкретно тут мы имеем определённые данные. Создаём интерфейс, где говорим, что у нас должно присутствовать свойство id
. Далее в функции нужно указать, что мы экстендим наш дженерик интерфейсом, который содержит нужное нам свойство. Аргументом и возвращаемым типом является массив от дженерика. Внутри функции реализована сортировка через switch
, который выбирает реализацию из указанной нами (asc-desc
)
065 Generic классы
Мы можем так же спокойно задавать дженерики для классов. Они будут определять какие типы данных мы будем передавать в конструктор класса.
Однако если мы не передадим второе значение (опциональное) и не определим типы, то второе значение дженерика будет unknown
Так же нужно сказать, что мы не можем напрямую наследоваться от класса с его дженериками. Мы можем наследоваться от класса и определить дженерики конкретными типами
Так же мы можем указать свои дженерики для наследуемого класса. Однако именам дженериков нельзя совпадать с родительскими
Дженерики для классов обычно задают, когда у нас свойства зависят от реализации
066 Mixins
Три типа наследования:
- Через
extends
- Композиция
- Прямое наследование
Это обычный подход при добавлении нового функционала в классы. Им мы обычно и пользуемся. Конкретно в этом примере такой подход оптимален.
И вот пример миксина. Сразу нужно сказать, что миксины используются редко и обычно используются в подходе DCI.
Что из себя представляет миксин? Миксин - это функция, которая возвращает класс, который мы расширили нужным нам классом.
Конкретно в нашем примере, TBase (дженерик) мы расширили через ListType
и этот TBase
определили как тип значения, которое мы передаём в функцию. Аргумент функции - это класс, которым мы будем расширять возвращаемый из функции класс.
Чтобы воспользоваться данной конструкцией, нам нужно сначала присвоить класс какой-то переменной, потом из этой переменной инстанциировать класс
Так же главной фишкой такого подхода является то, что мы можем заэкстендить дженерик сразу от нескольких тайпов
Связующим звеном для использования является AccordionList
- он содержит конструктор List
и свойство isOpen
(делается это так из-за того, что нам нужно передать класс, который имеет реализацию обоих тайпов → которые были сделаны из этих классов)
Преймущества миксинов:
- Он позволяет перенести функциональность сразу нескольких классов в один
- Позволяет примиксовать так же функциональность к исходному классу
- Тайпчекинг экстендедов класса
Когда использовать миксины?
Обычно миксины не используются и вместо них применяют композицию, чтобы перенести функциональность из класса в класс. Однако, если нам нужно будет перенести и функциональность из нескольких классов и свойства этих классов, то тут уже нужно будет использовать миксины