Рассмотрим использование паттерна headless component для создания модульных и гибких компонентов в React приложениях.
Разработка программного обеспечения — это непрерывный процесс. Самой большой проблемой в этом процессе является удобство сопровождения. Когда новые функции и улучшения начинают добавляться, это приводит к добавлению большего количества кода.
В этой статье мы рассмотрим простой паттерн в React, который позволяет создавать компоненты, ориентированные на будущее. Цель состоит в том, чтобы эти компоненты легко адаптировались к будущим изменениям, сохраняя при этом бизнес-логику отдельно от кода пользовательского интерфейса.
Выше приведен очень простой пример компонента. Представьте себе возможность, когда один и тот же шаблон интегрируется с более сложными приложениями. Шаблон headless component является чрезвычайно мощным, когда речь идет о создании перспективных компонентов с высокой степенью расширяемости.
Как это работает?
Суть этого шаблона заключается в том, чтобы отделить бизнес-логику от UI части самого компонента. Когда в будущем потребуется обновить пользовательский интерфейс, это можно сделать, не затрагивая код бизнес-логики. Паттерн, который мы собираемся использовать, называется headless component. В этом шаблоне компонент реализован в виде хуков React. Этот компонент отвечает за логику и управление состоянием. Headless-компонент ничего не знает о пользовательском интерфейсе. Создается отдельный компонент, который отвечает только за пользовательский интерфейс. Паттерн используется многими популярными библиотеками компонентов React, такими как: DownShift, React Table и т.д.Ваш первый headless компонент
Начнем с написания первого React компонента с использованием этого шаблона. Ниже приведен пример компонента dropdown:Давайте интегрируем наш headless-компонент с компонентом, отвечающим за обработку пользовательского интерфейса:const useDropdown = (items: Item[]) => { // состояние const [selectedIndex, setSelectedIndex] = useState<number>(-1); const [isOpen, setIsOpen] = useState(false); const [selectedItem, setSelectedItem] = useState<Item | null>(null); // функция возвращающая aria-атрибуты для UI const getAriaAttributes = () => ({ role: "combobox", "aria-expanded": isOpen, "aria-activedescendant": selectedItem ? selectedItem.text : undefined, }); const handleKeyDown = (e: React.KeyboardEvent) => { // реализация на основе switch-case }; const toggleDropdown = () => setIsOpen((isOpen) => !isOpen); return { isOpen, toggleDropdown, handleKeyDown, selectedItem, setSelectedItem, selectedIndex, }; };
const Dropdown = ({ items }: DropdownProps) => { const { isOpen, selectedItem, selectedIndex, toggleDropdown, handleKeyDown, setSelectedItem, } = useDropdown(items); return ( <div className="dropdown" onKeyDown={handleKeyDown}> <Trigger onClick={toggleDropdown} label={selectedItem ? selectedItem.text : "Select an item..."} /> {isOpen && ( <DropdownMenu items={items} onItemClick={setSelectedItem} selectedIndex={selectedIndex} /> )} </div> ); };