Советы и подсказки по созданию многократных компонентов пользовательского интерфейса

1656683892 sovety i podskazki po sozdaniyu mnogokratnyh komponentov polzovatelskogo interfejsa

Габриэль Коломбо

MtpKyjS5FI6RzBjGfV7kmEGuDdfyGLCd5Gks
Фото Фарзарда Назифи

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

Цели

Проще говоря, требования к созданию библиотеки таковы:

  1. Он должен быть продуктивным.
  2. Он должен быть ремонтопригодным.
  3. Оно должно быть последовательным.

Подходы

Минимизируйте бизнес-логику

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

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

Представьте, что мы создаем компонент кнопки.

Я хотел бы иметь возможность:

  • Сообщите, какой тип кнопки — Основная или обычная
  • Сообщите содержимое, отображаемое внутри кнопки (значок и текст)
  • Выключите или включите кнопку
  • Выполните определенное действие после нажатия

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

1 — Тип и содержимое зависят от компонента, поэтому их можно разместить в файле компонента.

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

const type = get(this, 'type');
const types = {  primary: 'btn--primary',  regular: 'btn--regular',}
return (type) ? types[type] : types.regular;

Мне нравится отображать свойства в объекте, потому что это позволяет масштабировать вещи без особого труда – на случай, если нам понадобится кнопка опасности или что-то подобное.

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

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

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

Отдельное состояние пользовательского интерфейса для многократного использования

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

  • Включить/выключить напр. кнопки, входы
  • Развернуть/Сжать — напр. свертывания, раскрывающиеся списки
  • Показать скрыть — Практически все

Эти свойства часто используются только для контроля визуального состояния – надеюсь.

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

/* UIStateMixin */
disable() {  set(this, ‘disabled’, true);
  return this;},
enable() {  set(this, 'disabled', false');
  return this;},

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

button  .disable()  .showLoadingIndicator();

Этот подход можно расширить. Он может принимать различные контексты и управлять внешними переменными вместо использования внутренних. Например:

_getCurrentDisabledAttr() {  return (isPresent(get(this, 'disabled')))    ? 'disabled'            /*  External parameter  */    : 'isDisabled';         /*  Internal variable   */},
enable(context) {  set(context || this, this._getCurrentDisabledAttr(), false);
  return this;}

Базовые функции абстрагирования

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

Эти методы по умолчанию можно также переместить в собственные миксины, например:

/* BaseComponentMixin */
_isCallbackValid(callbackName) {  const callback = get(this, callbackName);    return !!(isPresent(callback) && typeof callback === 'function');},
_handleCallback(callback, params) {  if (!this._isCallbackValid(callback)) {    throw new Error(/* message */);  }
  this.sendAction(callback, params);},

А затем включается в компоненты.

/* Component */
onClick(params) {  this._handleCallback('onClick', params);}

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

Составляющие компоненты

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

Например:

Base components: Button, dropdown, input.
Dropdown button => button + dropdownAutocomplete => input + dropdownSelect => input (readonly) + dropdown

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

Разделение проблем в самом лучшем виде.

Разделение забот

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

Скажем, мы создаем выбранный компонент.

{{form-select binding=productId items=items}}
items = [  { description: 'Product #1', value: 1 },  { description: 'Product #2', value: 2 }]

Внутри у нас есть простой компонент ввода и нисходящее меню.

{{form-input binding=_description}}
{{ui-dropdown items=items onSelect=(action 'selectItem')}}

Наша основная задача – представить описание пользователю, но это не имеет значения для нашей программы – имеет значение.

Выбирая опцию, вы разделяете объект, посылая описание на наш вход через внутреннюю переменную, одновременно придавая значение контроллеру, обновляя связанную переменную.

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

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

Предварительные настройки против новых компонентов

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

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

Трудно знать, когда использовать пресеты или создавать новые компоненты. Принимая это решение, я руководствуюсь следующими рекомендациями:

Когда создавать пресеты

1 — Шаблоны повторяющегося использования

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

/* Regular implementation */
{{form-autocomplete    binding=productId    url="products"            /*   URL to be fetched         */    labelAttr="description"   /*   Attribute used as label   */    valueAttr="id"            /*   Attribute used as value   */    apiAttr="product"         /*   Param sent on request     */}}
/* Presets */
{{form-autocomplete    preset="product"    binding=productId}}

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

/* Naive implementation of the presets module */
const presets = {  product: {    url: ‘products’,    labelAttr: ‘description’,    valueAttr: ‘id’,    apiAttr: ‘product’,  }, }
const attrs = presets[get(this, ‘preset’)];
Object.keys(attrs).forEach((prop) => {  if (!get(this, prop)) {    set(this, prop, attrs[prop]);  }});

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

2 — Базовый компонент слишком сложен

Когда базовый компонент, используемый для создания более конкретного компонента, принимает слишком много параметров. Таким образом, его создание приведет к определенным проблемам. Например:

  • Вам придется добавить большинство – если не все – параметры из нового компонента в базовый компонент. Поскольку все большее и большее количество компонентов происходит от него, любые обновления базового компонента будут отображать огромное количество изменений. Таким образом, это приводит к большему количеству ошибок.
  • Чем больше компонентов создается, тем труднее документировать и запоминать разные нюансы. Особенно это касается новых разработчиков.

Когда создавать новые компоненты

1 — Расширение функциональности

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

/* Declaration */
{{ui-button-dropdown items=items}}
/* Under the hood */
{{#ui-button onClick=(action 'toggleDropdown')}}  {{label}} <i class="fa fa-chevron-down"></i>  {{/ui-button}}
{{#if isExpanded}}  {{ui-dropdown items=items}}{{/if}}

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

2 — Настройки декорирования

Есть еще одна возможная причина создания новых компонентов. Это когда необходимо контролировать доступность параметров или декорировать значения по умолчанию.

/* Declaration */
{{form-datepicker onFocus=(action 'doSomething')}}
/* Under the hood */
{{form-input onFocus=(action '_onFocus')}}
_onFocus() {  $(this.element)    .find('input')    .select();                 /* Select field value on focus */
  this._handleCallback('onFocus'); /* Triggers param callback */}

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

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

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

В моем случае событие onBlur было заменено другим событием onChange. Это работает, когда пользователь заполняет поле или выбирает дату в календаре.

Вывод

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

Лучший результат приходит, когда каждый в группе делает то, что самое лучшее для себя и группы — Джон Нэш

Кроме того, не стесняйтесь спрашивать отзыв. Вы всегда найдете то, над чем можно работать.

Чтобы еще больше отточить свои навыки разработки программного обеспечения, я рекомендую посмотреть серию Эрика Эллиотта.Создание ПО». Это потрясающе!

Что ж, надеюсь, вам понравилась статья. Пожалуйста, возьмите эти концепции, превратите их в свои идеи и поделитесь ими с нами!

Также не стесняйтесь связаться со мной в твиттере @gcolombo_! Я хотел бы услышать ваше мнение и даже работать вместе.

Спасибо!

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *