Компонентный дизайн комбинированной формы

Компонентный дизайн комбинированной формы

Введение

Недавно, в ходе выполнения проекта, мы реализовали различные экраны форм на основе React Hook Form (RHF) и Material UI (MUI).

На начальном этапе проекта мы использовали общий компонент поля для поддержания согласованности пользовательского интерфейса форм.

<Field.Text
  name="name"
  label="Name"
/>

Разработчик мог быстро настраивать поля ввода аналогичным образом, используя только общий компонент без необходимости напрямую подключать React Hook Form и MUI.

На самом деле, сервис уже предоставлял различные компоненты ввода, такие как:

  • Field.Text

  • Field.Select

  • Field.Autocomplete

  • Field.DatePicker

  • Field.DateTimePicker

  • Field.Checkbox

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

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

Ограничения монолитной структуры

Изначально компоненты Field обрабатывали все задачи внутри одного компонента: вывод ярлыков, обработка валидации, подключение к React Hook Form и рендеринг ввода.

Field
├─ FormLabel
├─ Controller
└─ Input Component

Такая структура была удобна для использования на обычных экранах ввода.

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

UI с кнопкой рядом с Input

Ярким примером является UI, где кнопка располагается рядом с полем ввода.

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

image1.png

Поскольку существующий Field.Text объединяет Label, Input и HelperText в один компонент, простого выравнивания Flex было недостаточно для реализации нужной компоновки.

  • Использование align-items: flex-start выравнивает Label и кнопку на одной высоте

  • Использование align-items: center делает вертикальное выравнивание Input и кнопки неестественным

  • Использование align-items: flex-end в нормальном состоянии выглядит нормально, но когда появляется сообщение об ошибке, позиция кнопки смещается, и выравнивание нарушается

В конечном итоге, чтобы выровнять позицию кнопки, потребовалось хардкодирование margin-top: 22px с расчетом высоты и расстояния Label.

Это был пример того, что структура, в которой Label, Input и HelperText сильно связаны, не подходит для создания комплексного UI.

Проблема с HelperText диапазона ввода (Range Field)

Еще один пример – это UI диапазонного ввода.

Для пользователя диапазонный ввод выглядит как один элемент ввода, но реальная реализация состоит из двух полей ввода: начального и конечного значений.

image2.png

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

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

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

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

Случаи обхода общих компонентов.

По мере усложнения проекта в некоторых экранах стало сложно удовлетворить требования только с помощью существующих компонентов Field.

В итоге в некоторых экранах начали использовать подход, при котором общие компоненты не использовались, а вместо этого применялись Controller из React Hook Form и компоненты MUI напрямую.

<Controller
  name="period"
  control={control}
  render={({ field }) => (
    <TextField {...field} />
  )}
/>

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

Чтобы решить эту проблему, мы решили пересмотреть существующую структуру.

Причина, по которой была рассмотрена структура Composition.

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

Чтобы решить это, мы стали рассматривать паттерн Composition React.

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

Типичный пример следующий.

<Card>
  <Card.Header />
  <Card.Body />
  <Card.Footer />
</Card>

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

Такая структура часто используется и в Design System, а также имеет преимущества в виде высокой расширяемости и повторного использования.

Разделение Field по ролям

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

Мы разделили это по единицам ролей.

<Field>
  <Field.Label />
  <Field.Control />
  <Field.HelperText />
</Field>

Каждый компонент выполняет следующие роли.

  • Field: Компоновка макета и управление общим состоянием

  • Field.Label: Вывод метки

  • Field.Control: Связь с React Hook Form и рендеринг элементов ввода

  • Field.HelperText: Вывод сообщений об ошибках и 안내 текстов

Разделяя роли, мы четко определили ответственность компонентов и смогли гораздо гибче настраивать UI.

Наибольшее изменение — Field.Group

Самым важным изменением в этой рефакторинге стало внедрение Field.Group.

В реальных сервисах часто несколько элементов ввода имеют одно значение.

Типичный пример — UI выбора периода.

<Field>
  <Field.Label required>
   기간
  </Field.Label>

  <Field.Group>
   <Field.Control name="startDate" />
   <span>~</span>
   <Field.Control name="endDate" />
  </Field.Group>

  <Field.HelperText />
</Field>

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

С введением Field.Group стало возможным управлять несколькими элементами ввода как одним полем, а компоновка и обработка валидации также стали гораздо более естественными.

Комбинированная структура, обеспечивающая масштабируемость

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

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

<Field>
  <Field.Label>
    Tags
  </Field.Label>

  <Field.Control
    render={() => (
      <>
        <TextField />
        <ChipList />
      </>
    )}
  />

  <Field.HelperText />
</Field>

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

Результаты применения

После применения структуры Composition мы смогли достичь следующих эффектов.

  • Обеспечение согласованности структуры UI формы

  • Повышение способности к комплексному вводу UI

  • Легкость добавления новых типов ввода

  • Увеличение полезности slotProps MUI

  • Снижение обходного использования общих компонентов

  • Снижение затрат на техническое обслуживание

  • Увеличение возможности повторного использования

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

Заключение

Сначала я думал, что это просто работа по улучшению обертки компонента React Hook Form.

Но на самом деле более важной задачей было размышление о том, как спроектировать «общие компоненты для долгосрочной расширяемости».

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

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

nature

Site footer