5 лучших практик архитектуры React
Нет никаких сомнений в том, что React произвел революцию в создании пользовательских интерфейсов. Он прост в освоении и значительно облегчает создание многократно используемых компонентов, которые придают твоему сайту целостный вид и атмосферу.
Однако, поскольку React заботится только о слое представления приложения, он не применяет никакой конкретной архитектуры (например, MVC или MVVM). Это может затруднить организацию кодовой базы по мере роста твоего React-проекта.
1. Структура каталога
Первоначально стилизация и код наших компонентов были разделены. Все стили хранились в общем CSS-файле (мы используем SCSS для предварительной обработки). Сам компонент (в данном случае FilterSlider
) был отделен от стилей:
├── components │ └── FilterSlider │ ├── __tests__ │ │ └── FilterSlider-test.js │ └── FilterSlider.jsx └── styles └── photo-editor-sdk.scss
В ходе многочисленных рефакторингов мы обнаружили, что этот подход не очень хорошо масштабируется. В будущем наши компоненты должны будут использоваться совместно в нескольких внутренних проектах, таких как SDK и экспериментальный текстовый инструмент, который мы сейчас разрабатываем. Поэтому мы перешли на компонентно-ориентированную компоновку файлов:
components └── FilterSlider ├── __tests__ │ └── FilterSlider-test.js ├── FilterSlider.jsx └── FilterSlider.scss
Идея заключается в том, что весь код, принадлежащий компоненту (например, JavaScript, CSS, ресурсы, такие как картинки, тесты), находится в одной папке. Это позволяет легко извлечь код в модуль npm или, если ты торопишься, просто поделиться папкой с другим проектом.
Импорт компонентов
Одним из недостатков этой структуры каталогов является то, что при импорте компонентов необходимо импортировать полный путь, как показано ниже:
Но на самом деле мы хотели бы написать вот что:
Чтобы решить эту проблему, ты можешь создать index.js и сразу экспортировать его по умолчанию:
Другое решение немного сложнее, но оно использует стандартный механизм разрешения Node.js, что делает его надежным и перспективным. Все, что мы делаем, это добавляем файл package.json
в файловую структуру:
components └── FilterSlider ├── __tests__ │ └── FilterSlider-test.js ├── FilterSlider.jsx ├── FilterSlider.scss └── package.json
И в файле package.json
мы используем свойство main, чтобы установить точку входа в компонент, как показано ниже:
С этим дополнением мы можем импортировать такой компонент:
2. CSS в JavaScript
Стилизация, и особенно темы, всегда была некоторой проблемой. Как упоминалось выше, в первом варианте приложения у нас был большой файл CSS (SCSS), в котором жили все наши классы. Чтобы избежать коллизии имен, мы использовали глобальный префикс и следовали соглашениям BEM для создания имен правил CSS. Когда наше приложение разрослось, такой подход стал не очень хорошо масштабироваться, поэтому мы искали замену. Сначала мы оценили модули CSS, но в то время они имели некоторые проблемы с производительностью. Кроме того, извлечение CSS с помощью плагина webpack's Extract Text работало не так хорошо. Кроме того, такой подход создавал сильную зависимость от webpack и делал тестирование довольно сложным.
Далее мы оценили некоторые другие CSS-in-JS решения, которые недавно появились на рынке:
- Styled Components: самый популярный выбор с самым большим сообществом
- EmotionJS: сильный конкурент
- Linaria: решение с нулевым временем выполнения
Выбор одной из этих библиотек в значительной степени зависит от твоего сценария использования:
- Тебе нужна библиотека, чтобы выдавать скомпилированный CSS-файл для использования? EmotionJS и Linaria могут это сделать! Linaria даже не требует времени выполнения. Она сопоставляет реквизиты с CSS через переменные CSS, что исключает поддержку IE11 - но кому вообще нужен IE11? ;)
- Требуется ли ему запускаться на сервере? Это не проблема для последних версий всех библиотек.
Для структуры каталогов мы предпочитаем помещать все стили в файл styles.js
:
Таким образом, верстальщики также могут редактировать некоторые стили, не имея дела с React, но им придется изучить минимальный JavaScript и то, как сопоставить реквизиты с атрибутами CSS:
components └── FilterSlider ├── __tests__ │ └── FilterSlider-test.js ├── styles.js ├── FilterSlider.jsx └── index.js
Хорошей практикой является очистка основного файла компонента от HTML.
Стремление к единой ответственности компонентов React
Когда ты разрабатываешь высоко абстрактные компоненты пользовательского интерфейса, иногда трудно разделить проблемы. В некоторых моментах твоему компоненту потребуется определенная доменная логика из твоей модели, и тогда все пойдет наперекосяк. В следующих разделах я хочу показать тебе некоторые методы DRYing up компонентов (компонентов, следующих принципу "не повторяйся"). Следующие методы частично совпадают по функциональности и выбор подходящего для твоей архитектуры - это скорее предпочтение стиля, чем основанные на твердых фактах решения. Но сначала давай подумаем о примерах использования:
- Нам нужно внедрить механизм для работы с компонентами, контекст которых зависит от пользователя, вошедшего в систему.
- Нам нужно отобразить таблицу с несколькими складными элементами
<tbody>
. - Мы должны отображать различные компоненты в зависимости от различных состояний.
В следующем разделе я покажу различные решения для описанных выше проблем.
3. Кастомные хуки
Иногда тебе нужно убедиться, что компонент React отображается только тогда, когда пользователь вошел в приложение. Поначалу ты будешь делать некоторые проверки во время рендеринга, пока не обнаружишь, что часто повторяешься. Выполняя свою миссию по очистке кода от мусора, ты рано или поздно столкнешься с необходимостью написания пользовательских хуков. Не бойся: это не так уж сложно. Взгляни на следующий пример:
Хук useRequireAuth
будет проверять, вошел ли пользователь в систему, и в противном случае перенаправлять на другую страницу. Логика в хуке useAuth
может быть предоставлена через контекст или систему управления состоянием, например MobX или Redux.
4. Функция как дочерный элемент
Создание сворачиваемой строки таблицы - не самая простая задача. Как отобразить кнопку сворачивания? Как мы будем отображать дочерние элементы, когда таблица не свернута? Я знаю, что с JSX 2.0 все стало намного проще, так как ты можешь возвращать массив вместо одного тега, но я расширю этот пример, так как он иллюстрирует хороший случай использования паттерна "функция как дочерный элемент". Представь себе следующую таблицу:
И тело сворачиваемой таблицы:
Этот компонент можно использовать следующим образом:
Ты просто передаешь функцию в качестве дочерних компонентов, которая вызывается в родительском компоненте. Ты также мог видеть, что эта техника называется "render callback" (обратный вызов рендеринга) или в особых случаях, "render prop" (проп рендеринга).
5. Рендер пропс
Термин "render prop" был придуман Майклом Джексоном, который предположил, что шаблон компонента высшего порядка можно в 100% случаев заменить обычным компонентом с "render prop". Основная идея заключается в том, что все компоненты React - это функции, а функции можно передавать как props. Так почему бы не передавать компоненты React через props?!
Следующий код пытается обобщить способ получения данных из API. (Обрати внимание, что этот пример приведен только для демонстрации. В реальных проектах ты бы даже абстрагировал эту логику получения данных в хук useFetch
, чтобы еще больше отделить ее от пользовательского интерфейса). Вот код:
Как ты можешь видеть, здесь есть свойство render
, которое является функцией, вызываемой во время процесса рендеринга. Функция, вызываемая внутри нее, получает в качестве параметра полное состояние и возвращает JSX. Теперь посмотри на следующее использование:
Как видишь, параметры data
и isLoading
деструктурируются из объекта state и могут использоваться для управления ответом JSX. В данном случае, пока обещание не выполнено, отображается заголовок "Loading". Ты сам решаешь, какие части состояния передавать в render prop и как использовать их в своем пользовательском интерфейсе. В целом, это очень мощный механизм для извлечения общего поведения пользовательского интерфейса. Описанный выше паттерн function as children - это, по сути, тот же паттерн, где свойство - это дочерние элементы.
Совет: Поскольку шаблон render prop является обобщением шаблона function as children, ничто не мешает тебе иметь несколько render prop для одного компонента. Например, компонент Table
может получить render prop для рендеринга для заголовка, а затем еще один для тела.
Источник: https://www.sitepoint.com/react-architecture-best-practices/