Архитектура современного React приложения
2 года назад·6 мин. на чтение
Есть одна проблема, с которой сталкивается каждый React-разработчик на своем пути. Это то, как построить хорошую архитектуру приложения, какая структура приложения будет идеальной.
Эта статья поможет вам избежать некоторых распространенных ошибок, которые большинство разработчиков допускают при создании архитектуры приложений на реакте, и подскажет вам правильный способ структурирования директорий.
Прежде чем начать, необходимо подчеркнуть один момент: не существует идеального решения, которое подходит для любого возможного случая. Это особенно важно понимать, потому что многие разработчики всегда ищут единственное и неповторимое решение всех своих проблем.
Если вы попали сюда, это значит, что вы заинтересовались этой темой, так что пора начинать! Все содержимое, которое будет упоминаться, будет помещено в каталог
Обратите внимание, что это исключительный случай, если бы у нас была одна страница для аутентификации, нам не следовало бы помещать его в
Иначе обстоит дело со страницей
Вместо этого рассмотрите возможность использования каталога
src
, и каждая упомянутая новая папка будет располагаться относительно этой директории.
Компоненты
Что в первую очередь создает React-разработчик в проекте? React-приложения создаются с помощью компонентов. Существует много различных архитектур (некоторые очень хорошие, а другие ужасные) и есть весьма универсальный путь, который можно использовать в большинстве случаев, даже для небольших проектов. Вот как это выглядит:Ключевым моментом здесь является следующее: у нас есть папка├── components │ ├── common │ │ └── button │ │ ├── button.tsx │ │ ├── button.stories.tsx │ │ ├── button.spec.tsx │ │ └── index.ts │ └── signup-form │ ├── signup-form.tsx │ ├── signup-form.spec.tsx │ └── index.ts
components
, содержащая все компоненты, которые используются в приложении более одного раза, поэтому мы собираемся исключить все специфические компоненты из этой папки.
Почему? Просто потому, что смысл этой папки заключается в том, чтобы содержать логику многократного использования. Кнопка должна использоваться почти на каждой странице нашего приложения, поэтому и существует общая папка common
. Для компонента signup-form
происходит нечто иное. Почему?
Предположим, что у нас есть две разные страницы (подробнее об этом позже) для входа и регистрации, компонент signup-form
должен повторяться два раза, вот почему он помещен в папку components
.
components
.
Вы, наверное, также заметили, что каждый компонент помещен в соответствующую директорию с очень простым для понимания соглашением об именовании.
Это потому, что ваше приложение в конечном итоге может содержать более 1000 компонентов, и, если все они имеют тесты или файл storybook, это может легко превратиться в беспорядок. Давайте рассмотрим некоторые ключевые моменты этой папки:button ├── button.tsx ├── button.stories.tsx ├── button.spec.tsx └── index.ts
- Все файлы, связанные с этим компонентом, находятся в этой папке.
- Все экспортируемые модули помещаются в
index.ts
, чтобы избежать двойного имени в импорте. - Все файлы названы в kebab-case.
- Где находится компонент кнопки? -> В папке
button
. - Где находятся сторибуки для этой кнопки? -> В папке
button
. - Мне нужно найти тесты для этой кнопки, где я могу его найти? -> Очевидно, в папке
button
.
Страницы
Отдельной сущности для страниц в React не существует. Они тоже являются компонентами, состоящими из других компонентов. Но в отличие от других компонентов, обычно они очень строго привязаны (например, в определенный путь URL). Куда же их вставлять? Мы можем использовать каталогviews
(или pages
, если хотите), в который помещаются все эти вещи, например:
Дляviews ├── home.tsx ├── guestbook.tsx └── newsletter ├── index.ts ├── newsletter.tsx └── components └── newsletter-form ├── newsletter-form.tsx ├── newsletter-form.spec.tsx └── index.ts
home
и guestbook
все довольно просто, страница должна быть результатом композиции других компонентов, которые имеют соответствующие тесты, поэтому для них нет специального каталога.
newsletter
, на которой есть нечто специфическое, компонент newsletter-form
. В этом случае используется подход создания вложенной папки компонентов внутри папки страницы и действуем так же, как в обычной папке компонентов, используя те же правила.
Этот подход очень эффективен, поскольку позволяет разделить код на небольшие фрагменты, но при этом сохраняет хорошо организованную архитектуру. Компонент newsletter-form
не следует помещать в папку с общими components
, просто потому, что это единственное место, в котором он используется. Если приложение будет расти, и компонент будет использоваться в нескольких частях, ничто не помешает вам переместить его.
Еще один совет - сохранять согласованное имя между страницей и маршрутом, примерно так:
<Route path="/bookings"> <Route index element={<Bookings />} /> <Route path="create" element={<CreateBooking />} /> <Route path=":id" element={<ViewBooking />} /> <Route path=":id/edit" element={<EditBooking />} /> <Route path=":id/delete" element={<DeleteBooking />} /> </Route>
Лэйауты (Layouts, Макеты)
Лэйауты вообще не являются страницами, они больше похожи на компоненты, поэтому с ними можно обращаться так же, но лучше помещать их в папкуlayouts
, так понятнее, что в этом приложении есть n лэйаутов.
Вы можете заметить, что мы не называем ихlayouts ├── main.tsx └── auth.tsx
main-layout.tsx
, а просто main.tsx
, потому что, следуя этому шаблону, нам пришлось бы переименовать все компоненты, например, table-component.tsx
, что странно. Поэтому называем все компоненты без очевидного суффикса, задаваемого родительским каталогом, а если нужно подчеркнуть, что используется макет, всегда можно использовать псевдоним импорта.
import { Main as MainLayout } from "@/layouts/main.tsx";
Контексты, хуки и хранилища
Это довольно просто, и обычно, почти все разработчики придерживаются чего-то подобного:Опять же, для единообразия используется kebab-case для всех имен файлов, так что нам нужно беспокоиться о том, какие из них написаны заглавными буквами, а какие нет. Для тестовых файлов, из-за того, что пользовательских хуков немного, не обязательно создавать отдельную папку, но, если вы хотите быть очень строгими, вы можете сделать и это:hooks ├── use-users.ts └── use-click-outside.ts contexts ├── workbench.tsx └── authentication.tsx
hooks ├── use-users │ ├── use-users.ts │ ├── use-users.spec.ts │ └── index.ts └── use-click-outside.ts
Функции-помощники (хэлперы, helpers)
Сколько раз вы создавали красивую функциюformatCurrency
, не зная, куда ее положить? Папка helpers
придет вам на помощь.
Обычно сюда помещаются все файлы, которые используются для того, чтобы код выглядел лучше. Не важно, будет ли функция использоваться несколько раз или нет.
helpers ├── format-currency.ts ├── uc-first.ts └── pluck.ts
Константы
Существует много проектов, которые содержат константы в папкеutils
или helpers
, но лучше помещать их в отдельный файл, давая пользователю хороший обзор того, что используется в качестве константы в приложении. Чаще всего в эту папку помещаются только глобальные константы, поэтому не стоит помещать сюда константу QUERY_LIMIT
, если она используется только в одной функции для очень специфического случая.
Кроме того, можно хранить все константы в одном файле. Нет смысла разбивать каждую константу на отдельные файлы.constants └── index.ts
И использоваться они будут так:// @/constants/index.ts export const COMPLANY_EMAIL = "example@example.com";
import { COMPLANY_EMAIL } from "@/constants";
Стили
Просто поместите глобальные стили в папкуstyles
, и все готово.
А как насчет CSS для компонентов? Хороший вопрос. Помните папку компонентов, о которой мы говорили некоторое время назад? Так вот, вы можете добавить больше файлов в зависимости от ваших потребностей.styles ├── index.css ├── colors.css └── typography.css
Если вы используетеbutton ├── button.tsx ├── button.stories.tsx ├── button.styled.tsx ├── button.module.scss ├── button.spec.tsx └── index.ts
emotion
, styled-components
или просто CSS Modules, поместите их в папку конкретного компонента, чтобы все было оптимально упаковано.
Конфигурационные файлы
Есть ли у вашего приложения файлы конфигурации, такие как Dockerfiles, Fargate Task Definitions, webpack и т.д.? Папкаconfig
должна быть идеальным местом для них. Помещение их в соответствующую директорию позволяет избежать загрязнения корневого каталога не относящимися к делу файлами.
API
99% приложений react имеют хотя бы один вызов API к внешнему источнику данных (вашему бэкенду или какому-то публичному сервису), обычно эти операции выполняются в нескольких строках кода без особых сложностей, и именно поэтому, оптимальная их организация игнорируется. Рассмотрим этот код:Довольно просто, верно? Теперь представьте, что у вас есть эти 3 строки, распределенные по 10 компонентам, потому что вы часто используете именно этот адрес сервера. Надеюсь, вы не хотите выполнять поиск и замену всех URL в приложении, кроме того, если вы используете TypeScript, импортировать каждый раз тип ответа - это довольно повторяющееся действие.axios .get("https://api.service.com/bookings") .then((res) => setBookings(res.data)) .catch((err) => setError(err.message));
api
, который, прежде всего, содержит экземпляр клиента, используемого для вызовов, например, fetch
или axios
, а также файлы, содержащие декларации вызовов fetch
.
И пример файла users.ts:api ├── client.ts ├── users.ts └── bookings.ts
export type User = { id: string; firstName: string; lastName: string; email: string; }; export const fetchUsers = () => { return client.get<User[]>("/users", { baseURL: "https://api.service.com/v3/", }); };
Итоги
Это был долгий путь, и надеюсь, что информация в этой статье окажется полезной для вас при создании новых и существующих проектов. Еще многое предстоит сказать, всегда есть особые случаи, которые нужно принимать во внимание, но пункты, рассмотренные в этой статье, являются наиболее используемыми многими react разработчиками.Импорт и экспорт React компонентов
год назад·4 мин. на чтение
Как импортировать и экспортировать React компоненты
Содержание туториала по React
Магия компонентов заключается в возможности их повторного использования: вы можете создавать компоненты, состоящие из других компонентов. Но по мере того, как вы вкладываете все больше и больше компонентов, часто имеет смысл начать разбивать их на разные файлы. Это позволяет легко сканировать файлы и повторно использовать компоненты в большем количестве мест.
Здесь и
Когда вы пишете импорт по умолчанию, вы можете указать любое имя после импорта. Например, вместо этого вы можете написать
Затем импортируйте
Файл корневого компонента
В части "Пишем первый React компонент" мы создали компонентProfile
и компонент Gallery
, который его отображает:
В настоящее время они находятся в корневом файле компонента, который в этом примере называетсяfunction Profile() { return <img src="https://example.com/userpic.jpg" alt="User Name" />; } export default function Gallery() { return ( <section> <h1>Amazing scientists</h1> <Profile /> <Profile /> <Profile /> </section> ); }
App.js
. В Create React App ваше приложение находится в src/App.js
. Однако в зависимости от вашей настройки ваш корневой компонент может находиться в другом файле. Если вы используете фреймворк с файловой маршрутизацией, например Next.js, ваш корневой компонент будет разным для каждой страницы.
Экспорт и импорт компонента
Что, если в будущем вы захотите изменить посадочную страницу и разместить там список научных книг? Или разместить все профили в другом месте? Имеет смысл переместитьGallery
и Profile
из корневого файла компонента. Это сделает их более модульными и позволит повторно использовать их в других файлах. Вы можете переместить компонент в три шага:
- Создайте новый файл JS, чтобы поместить в него компоненты.
- Экспортируйте компонент из этого файла (используя экспорт по умолчанию или именованный экспорт).
- Импортируйте его в файл, в котором вы будете использовать компонент (используя соответствующую технику для импорта по умолчанию или именованного экспорта).
Profile
, и Gallery
были перемещены из App.js
в новый файл с именем Gallery.js
. Теперь вы можете изменить App.js
, чтобы импортировать галерею из Gallery.js
:
// App.js import Gallery from './Gallery.js'; export default function App() { return <Gallery />; }
Обратите внимание, как этот пример теперь разбит на два файла компонентов:// Gallery.js function Profile() { return <img src="https://example.com/userpic.jpg" alt="User Name" />; } export default function Gallery() { return ( <section> <h1>Amazing scientists</h1> <Profile /> <Profile /> <Profile /> </section> ); }
Gallery.js
:- Определяет компонент профиля, который используется только в том же файле и не экспортируется.
- Экспортирует компонент
Gallery
в качестве экспорта по умолчанию.
App.js
:- Импортирует
Gallery
по умолчанию изGallery.js
. - Экспортирует корневой компонент
App
в качестве экспорта по умолчанию.
- Импортирует
.js
, например:
Иimport Gallery from './Gallery';
'./Gallery.js'
, и './Gallery'
будут работать с React, хотя первое ближе к тому, как работают нативные модули ES.
Экспорт по умолчанию и именованный экспорт
Существует два основных способа экспорта значений с помощью JavaScript: экспорт по умолчанию и именованный экспорт. До сих пор в наших примерах использовался только экспорт по умолчанию. Но вы можете использовать один или оба из них в одном файле. Файл может иметь не более одного экспорта по умолчанию, но он может иметь сколько угодно именованных экспортов. То, как вы экспортируете свой компонент, определяет, как вы должны его импортировать. Вы получите сообщение об ошибке, если попытаетесь импортировать экспорт по умолчанию так же, как и именованный экспорт. Эта таблица поможет вам:Синтаксис | Ээкспортирование | Импортрирование |
---|---|---|
по умолчанию | export default function Button() {} | import Button from './button.js'; |
именованный | export function Button() {} | import { Button } from './button.js'; |
import Banana from './button.js'
, и он все равно предоставит вам тот же экспорт по умолчанию. Напротив, при именованном импорте имя должно совпадать с обеих сторон. Вот почему они называются именованным импортом.
Люди часто используют экспорт по умолчанию, если файл экспортирует только один компонент, и используют именованный экспорт, если он экспортирует несколько компонентов и значений. Независимо от того, какой стиль вы предпочитаете, всегда давайте осмысленные имена функциям компонентов и файлам, которые их содержат. Компоненты без имен, такие как export default() => {}
, не рекомендуются, поскольку они усложняют отладку.
Экспорт и импорт нескольких компонентов из одного файла
Что, если вы хотите показать только одинProfile
вместо галереи? Вы также можете экспортировать компонент Profile
. Но в Gallery.js
уже есть экспорт по умолчанию, и у вас не может быть двух экспортов по умолчанию. Вы можете создать новый файл с экспортом по умолчанию или добавить именованный экспорт для Profile
. Файл может иметь только один экспорт по умолчанию, но он может иметь множество именованных экспортов.
Чтобы уменьшить возможную путаницу между экспортом по умолчанию и именованным экспортом, некоторые команды предпочитают придерживаться только одного стиля (по умолчанию или именованного) или избегать их смешивания в одном файле. Это вопрос предпочтений. Делайте то, что лучше всего работает для вас.
Сначала экспортируйте Profile
из Gallery.js
, используя именованный экспорт (без ключевого слова default
):
export function Profile() { // ... }
Profile
из Gallery.js
в App.js
, используя именованный импорт (с фигурными скобками):
Наконец, отобразитеimport { Profile } from './Gallery.js';
<Profile />
из компонента App
:
Теперьexport default function App() { return <Profile />; }
Gallery.js
содержит два экспорта: экспорт Gallery
по умолчанию и именованный экспорт Profile
. App.js
импортирует их оба.
// App.js import Gallery from './Gallery.js'; import { Profile } from './Gallery.js'; export default function App() { return <Profile />; }
Теперь вы используете сочетание экспорта по умолчанию и именованного экспорта:// Gallery.js export function Profile() { return <img src="https://example.com/userpic.jpg" alt="User Name" />; } export default function Gallery() { return ( <section> <h1>Amazing scientists</h1> <Profile /> <Profile /> <Profile /> </section> ); }
-
Gallery.js
:- Экспортирует компонент
Profile
как именованный экспорт с именемProfile
. - Экспортирует компонент
Gallery
в качестве экспорта по умолчанию.
- Экспортирует компонент
-
App.js
:- Импортирует
Profile
как именованный импорт с именемProfile
изGallery.js
. - Импортирует
Gallery
как импорт по умолчанию изGallery.js
. - Экспортирует корневой компонент
App
в качестве экспорта по умолчанию.
- Импортирует
Резюме
На этой странице вы узнали:- Что такое файл корневого компонента
- Как импортировать и экспортировать компонент
- Когда и как использовать именованный импорт и экспорт; и импорт и экспорт по умолчанию
- Как экспортировать несколько компонентов из одного файла