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

Анатомия раскрывающегося списка

+--------------------------------------+
|                                      +--------> Dropdown
|  +--------------------------------+  |
|  |                                |  |
|  |                                +-----------> DropTrigger
|  |                                |  |
|  +--------------------------------+  |
|                                      |
|  +--------------------------------+  |
|  |                                +-----------> DropMenu
|  | +----------------------------+ |  |
|  | |                            | |  |
|  | |                            | |  |
|  | +----------------------------+ |  |
|  | +----------------------------+ |  |
|  | |                            | |  |
|  | |                            +-------------> DropItem
|  | +----------------------------+ |  |
|  |                                |  |
|  +--------------------------------+  |
|                                      |
+--------------------------------------+

Подход React с использованием ref

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

Итак, я реализовал свой собственный:

<Dropdown trigger={this.renderTrigger()} ref="dropdown">
  <a onClick={this.onSelect.bind(this, 1)}>Option 1</a>
  <a onClick={this.onSelect.bind(this, 2)}>Option 2</a>
</Dropdown>
...
onSelect(option) {
  this.props.onSelect(option);
  this.refs.dropdown.close()
}

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

Возьмите два: используйте контекст

Мы можем использовать анатомию раскрывающегося списка, чтобы составить его в React. Dropdown acts в качестве менеджера по расположению, размещая дочерние элементы в нужных местах и ​​создавая контекст, который будет использовать DropItem. Он добавляет поведение по умолчанию, такое как закрытие меню при щелчке вне раскрывающегося списка, и гарантирует, что триггер будет переключать видимость раскрывающегося меню при щелчке по нему. Он может обеспечивать согласованность CSS, а также применять темы, но это выходит за рамки этой публикации.

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

DropMenu, как и DropTrigger, - это просто заполнитель для Dropdown component, чтобы определить и разместить его дочерние элементы в нужном месте. Нет варианта использования, когда эти компоненты использовались бы вне Dropdown. Семантически не имеет смысла повторно использовать их где-либо еще.

Это позволит отображать настраиваемые триггеры для компонентов Select, например: отображать значение Select или количество выбранных элементов Multiselect. Вы также можете добавить другую разметку в DropMenu, например, ввод для поиска, чтобы реализовать функциональность элементов поиска и т. Д.

DropItem будет использовать контекст Dropdown, чтобы закрыть раскрывающийся список, если необходимо, на основе свойства closeOnClick. Он отправит onClick опору своим дочерним элементам, чтобы добавить к нему корневой / оборачивающий элемент. Это можно рассматривать как тесную связь, но у DropItem нет причин выходить за пределы Dropdown.

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

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

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