Русский
Русский
English
Статистика
Реклама

Component library

Taiga UI библиотека компонентов под Angular, которую вам стоит попробовать

12.01.2021 14:15:46 | Автор: admin

Привет!

Саша Инкин и я регулярно пишем на Хабр статьи по Angular. Почти все они основаны на нашем опыте разработки большой библиотеки компонентов.

Эту библиотеку мы развиваем, перерабатываем и дополняем уже несколько лет, а свои идеи проверяем на нескольких десятках проектов Тинькофф Бизнеса и внутренних систем компании. Мы рады сообщить: выложили нашу библиотеку в открытый доступ!

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

Как развивался наш UI Kit и как он организован

Если вам интересно узнать, зачем нам понадобилась отдельная библиотека компонентов и как она исторически развивалась, то сначала рекомендую прочитать крутейшую статью Юли Царевой Как организовать работу над библиотекой общих компонентов.

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

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

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

Эта часть обзавелась собственным дизайном и полной независимостью от контекста, а недавно оправдала свое название переездом в опенсорс. Давайте познакомимся с нашим UI Kit поближе!

Полностью модульный

Начнем с того, как организован проект. Taiga UI состоит из нескольких слоев, которые являются отдельными пакетами.

@taiga-ui/cdk

Фундаментальный пакет. Представляет собой набор директив, сервисов, токенов, абстрактных классов и утилит для абстрагирования и упрощения работы с Angular. Может быть использован как дополнительный мультитул с удобствами для вашего Angular-приложения любой сложности, а может и стать основой для построения собственного кита.

Пример сущностей:

  • TuiDestroyService для избавления от постоянного создания сабжектов destroy$ в компонентах.

  • TuiFilterPipe и TuiMapperPipe для обработки значений в шаблонах без лишних вызовов ChangeDetection.

  • Декоратор tuiPure для мемоизации значений геттеров и методов класса.

@taiga-ui/core

Пакет с базовыми компонентами для построения интерфейсов, а также с инструментами для основы веб-приложения. Это инструменты вроде рутового компонента, порталов для диалогов и выпадашек, настройки темизации и анимаций. Core задает вектор остальным пакетам c UI-элементами. На этом уровне появляется дизайн и все общие стили лежат тут.

@taiga-ui/kit

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

@taiga-ui/addon-*

Ряд тематических пакетов, которые основаны на первых трех. Например, есть пакет charts для графиков, commerce для работы с валютами, деньгами и вводом карт или даже отдельный пакет doc для построения собственной витрины аналогично нашей (ссылка на нее будет дальше).

Получается вот такая иерархия, при которой пакеты более высокого уровня строятся на базовых пакетах:

Вопрос: зачем тянуть в зависимости несколько пакетов, если я хочу лишь пару компонентов? Сколько они весят?

Несколько месяцев назад мы перешли от концепции единых точек входа в каждый пакет к полной разбивке всех пакетов на Secondary Entry Points. Чтобы сохранить удобный и привычный контракт импортов, мы устроили все пакеты по уникальной схеме, когда каждый последующий уровень вложенности является Secondary-поинтом к текущему, но Primary к сущностям своего содержимого.

Теперь все наши пакеты дают возможность импортировать сущности как из Primary точки входа, так и из любого уровня вложенности:

Причем первый вариант импорта достаточен, дополнительная вложенность автоматически разрешается на этапе сборки.

Такой подход к работе с Secondary Entry Points дает целый ряд плюсов в организации библиотек:

  • Бандл-приложений меньше, библиотека становится максимально tree shakable.

  • Любые циклические зависимости отлавливаются на этапе сборки.

  • Больше структурности в проекте, нет лишних связей между сущностями.

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

Кастомизируемый

Все стили и цвета задаются через CSS custom properties. Это позволяет легко собирать кастомные темы или даже подменять их в приложении на ходу.

Пока у нас доступна к выбору только одна основная тема, но мы планируем выпустить еще несколько стандартных и необычных вариантов.

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

Агностичный

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

Мы не завязываемся на интерфейсы, не ограничиваем визуальное отображение и думаем о том, как сделать компонент максимально гибким в использовании, а не продумываем все возможные юзкейсы. Подробнее о таких подходах можно почитать в статье Саши Инкина Компоненты-агностики в Angular.

Тем не менее мы контролируем базовый UX, чтобы вам не приходилось о нем думать: например, при фокусировке инпута с клавиатуры тултип покажет подсказку через секунду автоматически, чтобы screen reader прочитал ее:

Технологичный

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

Мы не боимся работать с DI, все наши компоненты в OnPush, а на всем проекте включен strict-режим TypeScriptа к типизации мы тоже относимся трепетно. Одним днем вы решите перейти на SSR, и наши компоненты в нем не сломаются.

Вы можете не беспокоиться о неожиданном значении, выходящем за рамки возвращаемого типа, а наши компоненты сами выведут ассерт в дев моде, если вы передадите в них что-то не то :)

Многообразный

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

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

Как начать использовать

Заходите на официальный сайт и в документацию библиотеки. Смотрите, изучайте и следуйте инструкциям:

taiga-ui.dev

Если вам хочется поддержать нас или было бы интересно понаблюдать за развитием библиотеки ставьте звездочку и подписывайтесь на Taiga UI в Github. Там можно задать любые вопросы, предложить идею или даже контрибьютить в код.

Еще с нами можно связаться в нашем канале в дискорде Angular. Общение там идет на английском языке, но при необходимости мы будем рады сделать отдельное коммьюнити и на русском.

Хотите узнать больше?

В ближайший четверг, 14 января, мы проведем стрим на нашем новом Twich канале. Начнем в 19-00 по МСК.

Расскажем больше про проект, презентуем его структуру, основные части и особенности. Постараемся ответить на любые ваши вопросы про Taiga UI, Angular или разработку библиотек компонентов.

Не прощаемся

Мы планируем писать более подробные статьи о том, как мы организуем и разрабатываем наши библиотеки. Такие статьи помогут вам лучше понимать работу нашего кита, а также объяснят разные приемы и практики разработки легко переиспользуемых компонентов на Angular, которые могут сильно выручить и в проектной разработке.

Поделитесь мнением о Taiga UI и расскажите, о каких компонентах, инструментах или процессах вам хотелось бы почитать в первую очередь?

Подробнее..

Упрощаем работу с Angular с помощью taiga-uicdk 5 наших лучших практик

11.05.2021 12:14:21 | Автор: admin

CDK базовый пакет библиотеки компонентов Taiga UI. Он не имеет никакой привязки к визуальной составляющей библиотеки, а скорее служит набором полезных инструментов для упрощения создания Angular-приложений.

Среди всех этих инструментов я выделил мою пятерку фаворитов. Я использую их во всех своих проектах и уже давно не представляю, как писать на Angular без них, потому что они ежедневно экономят мне массу времени.

Дисклеймер о весе библиотеки

Перед написанием статьи хотелось бы ответить на вопрос: Для чего тащить в бандл целый мультитул, когда мне нужна пара функций?

По результатам bundlephobia мы получим следующую картинку:

23 КБ результат не самый страшный, но и не очень приятный. Но все сущности наших библиотек лежат в отдельных Secondary Entry Point, что делает их полностью tree shakable. Это значит, что такой объем в бандле мы получим только в случае импорта и использования всех сущностей библиотеки в нашем приложении. Если вы импортите пару сущностей только они и попадут к вам в бандл, добавив к нему в результате меньше 1 КБ.

tuiPure продвинутая мемоизация вычислений

Это декоратор, который можно вешать на геттеры и чистые методы классов. Давайте разберем оба сценария.

Как геттер

Можно повесить декоратор tuiPure на геттер. В таком случае он позволяет сделать отложенные вычисления.

Пример 1. Скрываем и показываем сороковое число Фибоначчи, когда пользователь нажимает на кнопку

// template<div *ngIf="show">fibonacci(40) = {{ fibonacci40 }}</div>// component@tuiPureget fibonacci40(): number {  return calculateFibonacci(40);}

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

Пример 2. У нас есть компонент pull to refresh, который эмулирует поведение под iOS и Android. Один из его стримов вызывается только для Андроида, а для iOS нет. Завернем его в getter с pure, и ненужный Observable не будет создан для iOS.

<tui-mobile-ios-loader   *ngIf="isIOS; else angroidLoader"></tui-mobile-ios-loader><ng-template #angroidLoader>   <tui-mobile-android-loader       [style.transform]="loaderTransform$ | async"   ></tui-mobile-android-loader></ng-template>
@tuiPureget loaderTransform$(): Observable<string> {    return this.pulling$.pipe(        map(distance => translateY(Math.min(distance, ANDROID_MAX_DISTANCE))),    );}

Также можно обратиться к changes от ContentChild / ContentChildren: если мы вызываем такой геттер из шаблона, то уже можем быть уверены, что content готов. При соблюдении порядка также это можно провернуть и с ViewChild / ViewChildren.

Как метод

На метод тоже можно повесить декоратор @tuiPure. Тогда он будет работать следующим образом: при первом вызове метода посчитает значение и вернет его. Все последующие вызовы с теми же самыми аргументами будут возвращать уже посчитанный результат. Если аргумент изменится результат пересчитается.

Это полезно не только для сложных вычислений, но и в связке с геттером для создания непримитивов объектов или массивов.

get filteredItems(): readonly string[] {   return this.computeFilteredItems(this.items);}@tuiPureprivate computeFilteredItems(items: readonly string[]): readonly string[] {   return items.filter(someCondition);}

В этом случае можно вызвать геттер из шаблона, один раз отфильтровать items, и он будет возвращать тот же самый массив до тех пор, пока this.itemsне изменится. Это поможет избежать лишних операций пересоздания массива на каждый чих проверки изменений, а также проблем из-за постоянных изменений ссылки на массив при передаче дальше. При этом нам не придется, например, самим синхронизировать состояние в ngOnChanges, если this.items инпут компонента.

Документация по tuiPure

*tuiLet

Это простая структурная директива для объявления локальных переменных в шаблонах.

<ng-container *tuiLet="timer$ | async as time">   <p>Timer value: {{time}}</p>   <p>       It can be used many times:       <tui-badge [value]="time"></tui-badge>   </p>   <p>       It subsribed once and async pipe unsubsribes it after component destroy   </p></ng-container>

Вместо *tuiLetможно использовать *ngIf если вам не нужно показывать шаблон при falsy-значении (или если оно не предусмотрено). Но если вы работаете, например, с числами, то 0, скорее всего, является вполне адекватным значением. Тут и поможет *tuiLet

Документация по tuiLet

Метапайпы tuiMapper и tuiFilter

Мы создали пайп, чтобы не создавать другие пайпы, tuiMapper.

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

{{value | tuiMapper : mapper : arg1 : arg2 }}

Также удобно и преобразовывать данные для инпутов компонентов в шаблоне или использовать через *ngIf / *tuiLet:

<div    *ngIf="item | tuiMapper : toMarkers : itemIsToday(item) : !!getItemRange(item) as markers"    class="dots">    <div class="dot" [tuiBackground]="markers[0]"></div>    <div        *ngIf="markers.length > 1"        class="dot"        [tuiBackground]="markers[1]"    ></div></div>

Добавление цветных маркеров-точек в календарях @taiga-ui/core

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

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

Документация на mapper / документация на filter

destroy$

Это Observable-based сервис, который упрощает процесс отписки в компонентах и директивах.

@Component({   // ...   providers: [TuiDestroyService],})export class TuiDestroyExample {   constructor(     @Inject(TuiDestroyService)      private readonly destroy$: Observable<void>   ) {}   //    subscribeSomething() {       fromEvent(this.element, 'click')           .pipe(takeUntil(this.destroy$))           .subscribe(() => {               console.log('click');           });   }}

Все что нам нужно добавить его в providers компонента и заинжектить в конструкторе. Я предпочитаю писать типы сущностей из DI, которые минимально необходимы в компоненте. Здесь это Observable<void>. Но можно писать и покороче:

constructor(private destroy$: TuiDestroyService) {}

Кстати, сервис в такой ситуации привязывается не к лайфсайклу компонента, а к его DI Injectorу. Это может помочь в ситуации, когда нужно подписаться в сервисе или внутри DI фабрики. Такие кейсы довольно редки, но зато TuiDestroyService в них буквально спасает например, когда мы хотели дергать markForCheck из фабрики токена в статье о DI фокусах для проброса данных.

Ссылка на документацию

Плагины ng-event-plugins

Фактически это внешняя библиотека ng-event-plugins, которая поставляется вместе с cdk (прямая зависимость, которую не нужно устанавливать отдельно). Она добавляет свои обработчики к менеджеру плагинов Angular. В ней есть несколько очень полезных плагинов, которые добавляют ряд возможностей в шаблоны компонентов.

Например, .stopи .preventпозволяют декларативно делать stopPropagation и preventDefault на любой прилетающий ивент.

Было:

<some-input (mousedown)="handle($event)">    Choose date</some-input>
export class SomeComponent {   //    handle(event: MouseEvent) {      event.preventDefault();      event.stopPropagation();      this.onMouseDown(event);   }}

Стало:

<some-input (mousedown.prevent.stop)="onMouseDown()">    Choose date</some-input>

Или модификатор .silent который позволяет не запускать проверку изменений на событие:

<div (mousemove.silent)="onMouseMove()">    Callbacks to mousemove will not trigger change detection</div>

Можно отслеживать ивенты в capture-фазе с помощью .capture:

<div (click.capture.stop)="onClick()">    <div (click)="never()">Clicks will be stopped before reaching this DIV</div></div>

Все это работает и с @HostListenerами, и с кастомными событиями. Вы можете почитать подробнее в документации ng-event-plugins.

Итого

Мы посмотрели ряд сущностей пакета @taiga-ui/cdk. Надеюсь, какие-нибудь из них вам приглянулись и тоже будут помогать во всех дальнейших проектах!

Кстати, у меня еще есть статья про саму библиотеку Taiga UI, в которой описаны остальные пакеты и общая философия библиотеки.

Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru