Архитектура React: как структурировать и настроить приложение
Мне нравится обдумывать, как я организую свои приложения, чтобы их было легко использовать, понимать и расширять. Я собираюсь показать вам то, что я считаю интуитивно понятной и масштабируемой системой для крупномасштабных производственных приложений React. Основная концепция, которая для меня важна, — сделать архитектуру сфокусированной на функциях, а не на типах, организовать только общие компоненты на глобальном уровне и объединить все другие связанные объекты вместе в локализованном представлении.
Технические характеристики
Содержание статьи:
Поскольку эта статья будет тематической, я сделаю несколько заметок о том, какую технологию будет использовать проект:
Приложение — React
Глобальное управление состоянием — Redux, Redux Toolkit
React JS. Основы
Изучите основы ReactJS на практическом примере по созданию учебного веб-приложения
Получить курс сейчас!
Маршрутизация — React Router
Стили — стилизованные компоненты
Тестирование — Jest, Библиотека тестирования React
У меня нет твердого мнения о стилях, идеальны ли стилизованные компоненты, модули CSS или настройка Sass, но я думаю, что стилизованные компоненты, вероятно, являются одним из лучших вариантов для сохранения модульности ваших стилей.
Я также предполагаю, что тесты находятся рядом с кодом, а не в папке верхнего уровня tests. Я могу пойти любым путем, но для того, чтобы пример работал в реальном мире, необходимо принимать решения.
Все изложенное здесь может быть актуально, если вы используете ванильный Redux вместо Redux Toolkit. В любом случае я бы рекомендовала настроить Redux по методологии feature-sliced.
Я также неоднозначно отношусь к Storybook, но приведу пример его использования, если вы решите пользоватся ним в своем проекте.
В качестве примера я буду использовать пример «Приложение для библиотеки», в котором есть страница со списком книг, страница со списком авторов и система аутентификации.
Структура каталогов
Структура каталогов верхнего уровня будет следующей:
assets — глобальные статические assets, такие как изображения, SVG, логотип компании и т. д.
components — глобальные общие/повторно используемые компоненты, такие как макет (оболочки, навигация), компоненты формы, кнопки
services — модули JavaScript
store – глобальное хранилище Redux
utils — Утилиты, помощники, константы и т. д.
views — также могут называться «страницами», здесь будет содержаться большая часть приложения.
Мне нравится использовать такую структуру везде, где это возможно, поэтому все содержится в src, точкой входа является index.js а файл App.js настраивает аутентификацию и маршрутизацию.
. └── /src ├── /assets ├── /components ├── /services ├── /store ├── /utils ├── /views ├── index.js └── App.js
12345678910 | .└── /src ├── /assets ├── /components ├── /services ├── /store ├── /utils ├── /views ├── index.js └── App.js |
Возможно, у вас будут и дополнительные папки, например, types если это проект TypeScript, middleware если необходимо, возможно, context для контекста и т. д.
Псевдонимы
Я бы настроила систему на использование псевдонимов, чтобы все, что находится в папке components, можно было импортировать как @components, а в папке assets как @assetsи т. д. Если у вас есть собственный Webpack, это делается с помощью конфигурации разрешения.
JavaScript module.exports = { resolve: { extensions: [‘js’, ‘ts’], alias: { ‘@’: path.resolve(__dirname, ‘src’), ‘@assets’: path.resolve(__dirname, ‘src/components’), ‘@components’: path.resolve(__dirname, ‘src/components’), // …etc }, }, }
1234567891011 | module.exports = { resolve: { extensions: [‘js’, ‘ts’], alias: { ‘@’: path.resolve(__dirname, ‘src’), ‘@assets’: path.resolve(__dirname, ‘src/components’), ‘@components’: path.resolve(__dirname, ‘src/components’), // …etc }, },} |
Это значительно упрощает импорт из любого места в проекте и перемещение файлов без изменения импорта, и вы никогда не получите что-то вроде ../../../../../components/.
Компоненты
В папке components, я бы группировала по типу — forms, tables, buttons, layoutи т.д. Специфика будет варьироваться в зависимости от вашего конкретного приложения.
В этом примере я предполагаю, что вы либо создаете свою собственную систему форм, либо создаете свои собственные привязки к существующей системе форм (например, комбинируя Formik и Material UI). В этом случае, вам следует создать папку для каждого компонента (TextField, Select, Radio, Dropdownи т.д.), и внутри папки будет файл для самого компонента, стиля, теста, и Storybook, если они используются.
Component.js — фактический компонент React
Component.styles.js — файл стилизованных компонентов для компонента.
Component.test.js — Тесты
Component.stories.js — файл со Storybook
Для меня это имеет гораздо больше смысла, чем наличие одной папки, содержащей файлы для ВСЕХ компонентов, одной папки, содержащей все тесты, и одной папки, содержащей все файлы Storybook и т. д. Все сгруппировано вместе, и его легко найти.
React JS. Основы
Изучите основы ReactJS на практическом примере по созданию учебного веб-приложения
Получить курс сейчас! . └── /src └── /components ├── /forms │ ├── /TextField │ │ ├── TextField.js │ │ ├── TextField.styles.js │ │ ├── TextField.test.js │ │ └── TextField.stories.js │ ├── /Select │ │ ├── Select.js │ │ ├── Select.styles.js │ │ ├── Select.test.js │ │ └── Select.stories.js │ └── index.js ├── /routing │ └── /PrivateRoute │ ├── /PrivateRoute.js │ └── /PrivateRoute.test.js └── /layout └── /navigation └── /NavBar ├── NavBar.js ├── NavBar.styles.js ├── NavBar.test.js └── NavBar.stories.js
1234567891011121314151617181920212223242526 | .└── /src └── /components ├── /forms │ ├── /TextField │ │ ├── TextField.js │ │ ├── TextField.styles.js │ │ ├── TextField.test.js │ │ └── TextField.stories.js │ ├── /Select │ │ ├── Select.js │ │ ├── Select.styles.js │ │ ├── Select.test.js │ │ └── Select.stories.js │ └── index.js ├── /routing │ └── /PrivateRoute │ ├── /PrivateRoute.js │ └── /PrivateRoute.test.js └── /layout └── /navigation └── /NavBar ├── NavBar.js ├── NavBar.styles.js ├── NavBar.test.js └── NavBar.stories.js |
Вы заметите, файл index.js в каталоге components/forms. Справедливо предлагается избегать использования index.js файлов, поскольку они не являются явными, но в этом случае это имеет смысл — в конечном итоге они будут индексом всех форм и будут выглядеть примерно так:
JavaScript // src/components/forms/index.js import { TextField } from ‘./TextField/TextField’ import { Select } from ‘./Select/Select’ import { Radio } from ‘./Radio/Radio’ export { TextField, Select, Radio }
123456 | // src/components/forms/index.jsimport { TextField } from ‘./TextField/TextField’import { Select } from ‘./Select/Select’import { Radio } from ‘./Radio/Radio’ export { TextField, Select, Radio } |
Затем, когда вам нужно использовать один или несколько компонентов, вы можете легко импортировать их все сразу.
JavaScript import { TextField, Select, Radio } from ‘@components/forms’
1 | import { TextField, Select, Radio } from ‘@components/forms’ |
Я бы рекомендовала этот подход больше, чем создание index.js внутри каждой папки каталога forms, так что теперь у вас есть только один index.js который фактически индексирует весь каталог, а не десять файлов index.js, чтобы упростить импорт для каждого отдельного файла.
Сервисы
Каталог services менее важен, чем components, но если вы делаете простой модуль JavaScript, который использует остальная часть приложения, это может быть удобно. Распространенным надуманным примером является модуль LocalStorage, который может выглядеть так:
. └── /src └── /services ├── /LocalStorage │ ├── LocalStorage.service.js │ └── LocalStorage.test.js └── index.js
1234567 | .└── /src └── /services ├── /LocalStorage │ ├── LocalStorage.service.js │ └── LocalStorage.test.js └── index.js |
Пример использования services:
JavaScript // src/services/LocalStorage/LocalStorage.service.js export const LocalStorage = { get(key) {}, set(key, value) {}, remove(key) {}, clear() {}, }
1234567 | // src/services/LocalStorage/LocalStorage.service.jsexport const LocalStorage = { get(key) {}, set(key, value) {}, remove(key) {}, clear() {},} |
JavaScript import { LocalStorage } from ‘@services’ LocalStorage.get(‘foo’)
123 | import { LocalStorage } from ‘@services’ LocalStorage.get(‘foo’) |
Store
Глобальное хранилище данных будет находиться в каталоге store — в данном случае Redux. У каждой функции будет папка, которая будет содержать фрагмент Redux Toolkit, а также тесты. Эту настройку можно использовать с обычным Redux, вам просто нужно создать файлы .reducers.js и .actions.js вместо slice. Если вы используете библиотеку saga, это может быть .saga.js вместо .actions.js для Redux Thunk.
. └── /src ├── /store │ ├── /authentication │ │ ├── /authentication.slice.js │ │ ├── /authentication.actions.js │ │ └── /authentication.test.js │ ├── /authors │ │ ├── /authors.slice.js │ │ ├── /authors.actions.js │ │ └── /authors.test.js │ └── /books │ ├── /books.slice.js │ ├── /books.actions.js │ └── /books.test.js ├── rootReducer.js └── index.js
1234567891011121314151617 | .└── /src ├── /store │ ├── /authentication │ │ ├── /authentication.slice.js │ │ ├── /authentication.actions.js │ │ └── /authentication.test.js │ ├── /authors │ │ ├── /authors.slice.js │ │ ├── /authors.actions.js │ │ └── /authors.test.js │ └── /books │ ├── /books.slice.js │ ├── /books.actions.js │ └── /books.test.js ├── rootReducer.js └── index.js |
Вы также можете добавить что-то вроде ui раздела хранилища для обработки модальных окон, переключения боковой панели и другого глобального состояния пользовательского интерфейса, что, на мой взгляд, лучше, чем повсюду иметь const [isOpen, setIsOpen] = useState(false).
В rootReducer вы импортируете все свои фрагменты и объедините их с combineReducers, а в index.js вы настроите хранилище.
Утилиты
В зависимости от того, нужна ли вашему проекту папка utils, решать вам, но я думаю, что обычно есть некоторые глобальные служебные функции, такие как проверка и преобразование, которые можно легко использовать в нескольких разделах приложения. Если вы хотите получить хорошо организованный проект, а не просто тысячи функций в одном файле helpers.js, это может стать полезным дополнением к организации проекта.
. └── src └── /utils ├── /constants │ └── countries.constants.js └── /helpers ├── validation.helpers.js ├── currency.helpers.js └── array.helpers.js
123456789 | .└── src └── /utils ├── /constants │ └── countries.constants.js └── /helpers ├── validation.helpers.js ├── currency.helpers.js └── array.helpers.js |
Опять же, папка utils может содержать все, что хотите, то, что по вашему мнению, имеет смысл сохранить на глобальном уровне. Если вы не предпочитаете «многоуровневые» имена файлов, вы можете просто назвать файл validation.js, но, на мое мнение, явное выражение ничего не отнимает у проекта и упрощает навигацию по именам файлов при поиске в вашей среде IDE.
Views
Здесь будет находиться основная часть вашего приложения: в каталоге views. Любая страница в вашем приложении — это «view». В этом небольшом примере представления довольно хорошо совпадают с хранилищем Redux, но не обязательно, чтобы хранилище и представления были абсолютно одинаковыми, поэтому они разделены. Также books может быть получен из authors и так далее.
Все, что находится в представлении, — это элемент, который, скорее всего, будет использоваться только в этом конкретном представлении — BookForm будет использоваться только при маршруте /books, и AuthorBlurb будет использоваться только при маршруте /authors. Views может включать определенные формы, модальные окна, кнопки, любой компонент, который не будет глобальным.
Преимущество того, чтобы все было сфокусировано на предметной области, вместо того, чтобы объединять все ваши страницы вместе в components/pages заключается в том, что это позволяет легко взглянуть на структуру приложения и узнать, сколько существует представлений верхнего уровня, и узнать, где находятся все представления, которые используется текущим. Если есть вложенные маршруты, вы всегда можете добавить вложенную папку views в основной маршрут.
. └── /src └── /views ├── /Authors │ ├── /AuthorsPage │ │ ├── AuthorsPage.js │ │ └── AuthorsPage.test.js │ └── /AuthorBlurb │ ├── /AuthorBlurb.js │ └── /AuthorBlurb.test.js ├── /Books │ ├── /BooksPage │ │ ├── BooksPage.js │ │ └── BooksPage.test.js │ └── /BookForm │ ├── /BookForm.js │ └── /BookForm.test.js └── /Login ├── LoginPage │ ├── LoginPage.styles.js │ ├── LoginPage.js │ └── LoginPage.test.js └── LoginForm ├── LoginForm.js └── LoginForm.test.js
12345678910111213141516171819202122232425 | .└── /src └── /views ├── /Authors │ ├── /AuthorsPage │ │ ├── AuthorsPage.js │ │ └── AuthorsPage.test.js │ └── /AuthorBlurb │ ├── /AuthorBlurb.js │ └── /AuthorBlurb.test.js ├── /Books │ ├── /BooksPage │ │ ├── BooksPage.js │ │ └── BooksPage.test.js │ └── /BookForm │ ├── /BookForm.js │ └── /BookForm.test.js └── /Login ├── LoginPage │ ├── LoginPage.styles.js │ ├── LoginPage.js │ └── LoginPage.test.js └── LoginForm ├── LoginForm.js └── LoginForm.test.js |
Хранение в папках может показаться раздражающим, если вы никогда не настраивали свой проект таким образом — вы всегда можете сохранить его менее многоуровневым или перенести tests в отдельный каталог, который имитирует остальную часть приложения.
Заключение
Это мое предложение по системе организации React, которая хорошо масштабируется для большого производственного приложения, включает в себя тестирование и стили, а также объединяет все вместе с фокусировкой на функциях. Она более вложена, чем традиционная структура того, что находится внутри components и containers, но эта система немного устарела из-за того, что Redux намного проще реализовать с помощью хуков, и «умные» контейнеры и «глупые» компоненты больше не нужны.
Легко взглянуть на эту систему и понять все, что необходимо для вашего приложения, и где работать над конкретным разделом или компонентом, который влияет на приложение в глобальном масштабе. Эта система может не подходить для всех типов приложений, но у меня она сработала. Я хотела бы услышать какие-либо комментарии о способах улучшения этой системы или других заслуживающих внимания системах.
Автор: Tania Rascia
Источник: webformyself.com