Всем привет, меня зовут Роман Пятаков! Я технический руководитель фронтенд-команды Lamoda. И сегодня я хочу поговорить с вами о разработке сложных компонентов.
Lamoda — технически сложный продукт, которым ежемесячно пользуются 10 миллионов пользователей и имеющий более 100 внутренних подсистем.
Верхушкой этого айсберга является интерфейс интернет-магазина, или фронтенд. Наша команда разрабатывает и поддерживает UI для десктопных и мобильных сайтов, те части нативных приложений для iOS и Android, которые сделаны на WebView, а также различные маркетинговые «надстройки» (баннеры и лендинги).
В своей работе мы, конечно, используем комплектующие.
Нам встречаются самые разные компоненты, но есть и такие, которые по праву можно назвать сложными.
Вы сразу подумали об утонченных формах или эффектной графике? Я вас удивлю, но даже обычное модальное окно может создать проблемы в разработке.
Около года назад на мои плечи легла задача разработки этого, казалось бы, простого компонента, что в итоге вылилось в три календарных месяца работы.
К счастью, из этого неловкого опыта я извлек три урока — три принципа, которыми я сегодня с вами поделюсь, чтобы вы не наступали на одни и те же грабли и сделали свою работу более эффективной.
Сразу оговорюсь, что в нашей компании мы используем стек на базе Vue.js. Но если у вас React или Angular, эта статья все равно будет полезна, поскольку выделенные мной принципы универсальны.
Принцип №1: «Компонент — это песочница»
На этом слайде показано модальное мобильное окно до и после того, как продукт получил задание провести A/B-тестирование и улучшить UX. В новой версии модальное окно перемещается снизу и не занимает весь экран.
Пользователь может в любой момент смахнуть его и вернуться к тому месту, с которого начал.
При разработке этого компонента у меня возник вопрос: как сделать свайп? Необходимо, чтобы модал не только свайпал вверх, но и следовал за движением пальца.
К сожалению, на GitHub не оказалось необходимой библиотеки или готового решения.
Библиотеки типа Hammer.js нам не подходят, поскольку они умеют распознавать только базовые жесты.
Но я не хотел изобретать велосипед и создавать JavaScript с нуля.
И у меня возникла идея использовать библиотеку Swiper.js, который уже использовался на нашем сайте для сенсорных галерей продуктов.
Swiper соответствовал требованиям задачи, так как может проводить пальцем и следовать за пальцем.
Обычные галереи на нашем сайте работают по простому принципу: есть последовательность слайдов, которые мы переключаем свайпом.
Я решил проявить креативность и предложил перевернуть галерею, сделав ее импровизированной из двух слайдов, занимающих весь экран.
Один слайд пустой, другой содержит модальный фрейм.
Таким образом, мы можем контролировать видимость окна, проводя слайды.
Swiper — небезопасная среда для детей
Казалось бы, все работает и все хорошо.
Но потом начали возникать проблемы.
У нас есть модал с окном авторизации, где пользователь авторизуется в сервисе.
Вот капча Google, которая, в свою очередь, использует очень избитый шаблон — положение: фиксированное.
Это необходимо для того, чтобы расположиться в правом нижнем углу.
Проблема в том, что Swiper использует преобразование CSS для перемещения слайдов.
Это означает, что теперь он становится блоком, содержащим капчу, и позиция:fixed рассчитывается на основе его координат. В результате наша капча, как марионетка, будет следовать за движениями Свайпера и «улетать» с экрана.
То есть ее поведение становится совершенно неуправляемым, что нас, конечно, не устраивает. Вот пример другого случая.
Внутри такого модального окна находится галерея, которая также использует Swiper.
Чтобы внутренняя прокрутка работала и не было конфликтных событий касания, отключаем свайп внутри окна и оставляем его в верхней затемненной части.
Однако Swiper работает так, что перехватывает события касания, и они больше не попадают во внутренний Swiper. В результате мы лишены функционала галереи и вынуждены либо отключить свайп, либо галерею.
И тут я пришёл к выводу, что использовать Swiper здесь не лучшее решение.
В результате мы получили промежуточный слой между нашим компонентом и его дочерними элементами.
Это привело к созданию различных побочных эффектов, косвенно влияющих на расположение элементов и возможность использования компонентов.
Альтернативное решение
Есть такое нативное решение, как CSS Scroll Snap. Сейчас эта технология находится в нашем проекте на стадии тестирования, но мы ожидаем, что все будет работать абсолютно прозрачно и никак не повлияет на наши дочерние компоненты.
.
modal { scroll-snap-type: y mandatory; } .
modalFrame, .
emptyFrame { scroll-snap-align: start; }
function onScroll(event) {
if (event.target.scrollTop === 0) {
// modal is closed
}
}
Всего несколько строк CSS и все готово.
В JavaScript мы можем легко проверить, что модальное окно было удалено с экрана, используя простой и простой обработчик.
Заключение: Из этого опыта я понял, что стоит убедиться, что компоненты не создают никаких помех или побочных эффектов своим дочерним компонентам.Но есть и другая сторона медали.Это иначе называется принципом ортогональности или низкой связи в программировании.
Это особенно важно для базовых компонентов пользовательского интерфейса, таких как модальное окно.
Если мы будем следить за этим, мы получим компоненты, которые будут работать как часы и их будет легче использовать повторно.
Такое «чистое» решение, как CSS Scroll Snap, не всегда лежит на поверхности.
Чтобы найти его, потребуется время.
Поскольку это нативное решение, оно может работать не везде.
В этом случае придется идти на компромиссы.
Например, пожертвовав такой важной функцией модального окна, как свайп.
Принцип №2: «Причудливая функция может подождать» У нас есть модальное окно фильтров, которое прикрыто другим окном с выбором цвета.
Получается, что верх полностью закрывает низ.
И только тонкая серая полоска - индикатор - показывает пользователю, что под одним модалом есть еще один.
Да, мне удалось реализовать эту возможность, но код компонента стал намного сложнее, потому что это было необходимо:
- Поддерживайте порядок модальных окон.
- Узнайте высоту содержимого.
- Создайте хранилище данных.
- Рассчитать видимость.
Например, в окне с галереей товаров, о котором я говорил чуть выше.
Дело в том, что индикатор реализован как псевдоэлемент ::before. Фотография в галерее использует свойство overflow:hidden для обрезки верхнего края.
В результате псевдоэлемент также усекается.
То есть физически наш индикатор остаётся в документе, но на экране его уже нет. В результате мы потеряли свою функцию, и релиз пришлось отложить.
Да, функция полезная и классная, но не критичная.
Без этого вполне можно было обойтись.
Было бы неплохо, если бы это было легко реализовать, но для усложнения кодовой базы все равно придется приложить немало усилий.
И в дальнейшем я решил не наступать на эти грабли.
У нас был такой случай, когда я разрабатывал полноэкранное окно — поле ввода.
А по задумке дизайнера нужно было, чтобы окно ввода закрывалось по нажатию кнопки готово на экранной клавиатуре.
И возникла проблема: как отследить этот клик в браузере? Родные приложения позволяют легко с этим работать, но в браузере это никак не отследишь, кроме как костылями.
Чтобы не повторять прошлых ошибок, я сразу обратился к дизайнеру, и мы пришли к компромиссному решению: включить эту кнопку явно в интерфейс.
Да, получилось не очень хорошо, но эффективно.
Заключение: Я понял, что важно вовремя обнаружить, что функция в компоненте — это необычная функция.Если это действительно «кровь из носа» и какую-то фичу нужно сделать здесь и сейчас, то стоит прибегнуть к подходу Git Successful Flow. В этом случае мы можем порыться в ветке разработки с кодом компонента без этой навороченной функции.Да, это круто, но его реализация ставит большие задачи.
В этом случае лучше найти компромиссное решение: упростить фичу или перенести ее на следующую итерацию.
Соблюдение этого принципа ускорит выпуск релизов, упростит внедрение компонента и, как следствие, его будет легче поддерживать и добавлять новые функции в будущем.
Это, по крайней мере, позволит избежать задержек в работе команды.
Принцип №3: «Не объединяйтесь и не побеждайте» Однажды прибежал наш тимлид и сказал: «Ребята, Рома сделал такое, что модал теперь может всё!» И я действительно неплохо его накачал вот так:
- Реализована поддержка старого и нового дизайна A/B-тестов.
- Добавлены интересные функции: индикатор перекрытия, свайп, различные анимации.
- Появилась возможность кастомизации — поддержка разных тем для разных случаев использования.
- Стала возможна поддержка прелоадера и виртуальных списков.
На мой взгляд, получилось слишком.
И ситуация может стать еще хуже.
Дело в том, что помимо мобильного модального окна у нас есть еще и десктопное.
В наши планы входило создание адаптивного модального окна XModal, которое было бы либо мобильным, либо настольным, в зависимости от того, какой сейчас экран.
Если мы решим эту проблему, то получим компонент-монстр.
А именно:
- К объему кода добавится еще +50%.
- Давайте подробно разберемся с «если», так как будет добавлено множество разных условий.
- Мы перебьем мобильные стили десктопными, что приведет к полному хаосу в CSS.
- Будут конфликты в кодовой базе и, как следствие, могут возникнуть конфликты между командами мобильных и настольных компьютеров.
А также привести их API к одному типу и автоматически переключаться на нужную реализацию незаметно для родительского компонента.
И вот как это выглядит в коде: import MModal from ‘components/m-modal/m-modal.vue’;
import DModal from ‘components/d-modal/d-modal.vue’;
import deviceProps from ‘utils/device-props’;
const XModal = {
functional: true,
render(createElement, context) {
return context.parent.$createElement(
deviceProps.screenSize === ‘phone’ ? MModal : DModal,
context.data,
context.children,
);
},
};
Поначалу в команде был некоторый скептицизм, потому что будет копипаста.
Но потом люди оценили и поняли, что когда у нас есть серьезные различия между версией А и версией Б, то действительно лучше не сливаться.
И этот подход может пригодиться нам для A/B-тестирования или в случае нескольких сред (сайт и мобильное приложение).
Мы пришли к общему решению в виде Функциональный компонент представляет собой прозрачный клей, который не хранит никакого состояния и не виден даже в инструментах разработки.
Таким образом, у нас есть прокси-компонент и selectorFunction, инкапсулирующий критерий, по которому мы выбираем одну из реализаций компонента.
Если кто-то еще не догадался, то это Прокси-шаблон структурного проектирования .
Заключение : в случае разных реализаций компонента лучше не объединять их, а оставить жить отдельно.Другая сторона медали в том, что копипаста здесь не избежать.Шаблон Proxy действует как прозрачный клей, который скрепляет различные реализации компонента, оставляя клиента в неведении относительно того, как все работает. Логика компонентов существенно упрощается, не будет путаницы в стилях, а команды смогут работать самостоятельно.
В некоторых случаях его будет настолько много, что вам придется отказаться от использования этого подхода.
Также могут возникнуть проблемы с приборами.
Например, ваша любимая IDE не сможет распознать API из-за экрана прокси и не поможет вам с автозаполнением.
выводы Поэтому не следует недооценивать разработку компонентов.
Даже примитивное модальное окно может создать серьезные проблемы с тем, как это сделать и как реализовать.
Принципы, описанные в статье, позволят вам:
- Убедитесь, что нет промежуточных слоев, которые могут создавать помехи или побочные эффекты для «детей».
- Распознайте необычные функции, которые не являются критичными, но довольно сложно реализовать.
- Если существует несколько реализаций компонента, не смешивайте все в одну кучу, а используйте прокси-шаблон, чтобы склеить части в один общий компонент.
Теги: #программирование #разработка веб-сайтов #фронтенд-разработка #vue.js #компоненты #шаблоны проектирования
-
Обзор: Cloudmark Spamnet
19 Oct, 24 -
Бортовая Сеть Велосипеда
19 Oct, 24 -
Прогрессивная Шкала Налогообложения
19 Oct, 24 -
Откуда Взялось Комплексное Число?
19 Oct, 24