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

В чем преимущества структуры проекта?

  1. Членам команды не нужно спорить, где разместить тот или иной функционал.
  2. Можно описать, чтобы не приходилось каждый раз объяснять новичкам
  3. С большим количеством фронтенд-проектов с той же структурой легко поменять местами членов команды.
  4. Вы можете сосредоточиться на функциональности, а не думать о структуре

Что должна выполнять хорошая структура?

  1. Легкость
  2. Гибкость
  3. Возможность повторного использования
  4. Повысить продуктивность

Лучшие практики использования компонентов

Именование файлов компонентов

  • имя каталога компонентов PascalCase
  • имя файла компонента PascalCase
  • НЕ повторяйте имя родителя
  • ПЛОХО: src / Banners / BannersEdit / BannersEditForm.tsx
  • ХОРОШО: src / Banners / Edit / Form.tsx

Именование компонентов

Имя ваших компонентов должно копировать путь к компоненту, например.

  1. src / pages / Login / index.tsx - ‹Login /›
  2. src / pages / Login / Form.tsx - ‹LoginForm /›

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

Именование компонентов контейнера

Если вы хотите экспортировать обернутый компонент с помощью React.memo () или compose (), добавьте Component суффикс к определению вашего класса и экспортируйте const с именем без суффикса.

ИЗБЕГАЙТЕ создания компонентов с зависимым состоянием на реквизитах - кроме начальных значений

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

Компонент React - setState ()

setState () всегда будет приводить к повторному рендерингу, если shouldComponentUpdate () не вернет false

Это означает, что вы должны использовать setState () для изменения состояния компонентов и никогда не изменять состояние напрямую. Также нам нужно знать, что когда мы работаем с составными типами, мы делимся ссылками.

Пример:

Здесь мы напрямую изменяем свойства propertyOne в состоянии.

Решение без новой библиотеки:

Дополнительная литература:

ИЗБЕГАЙТЕ создания новых ссылок в render () - используйте функции-обработчики

Пример - создание нового объекта и анонимной функции и передача их в качестве свойств:

Это приведет к тому, что каждый раз, когда ParentComponent re-renders, ChildComponent re-renders также будет.

Почему? Это потому, что в каждом цикле рендеринга ParentComponent мы создаем новый объект и функцию, и вы передаете эти вновь созданные объекты, поэтому ссылки на свойства всегда меняются.

Как написать функции обработчика событий, чтобы избежать новых ссылок

В случае, когда нам нравится использовать только одно примитивное значение:

В случае, когда нам нравится использовать непримитивные значения (объекты, массивы):

Используйте React.PureComponent и React.memo

Почему? Это потому, что React.PureComponent и React.memo по умолчанию реализуют неглубокое сравнение свойств и состояний. React.memo - это версия React.PureComponent для функциональных компонентов. Неглубокое сравнение означает, что свойства и состояния (до изменений и после изменений) сравниваются только на 0-м уровне. Для примитивных типов сравниваются их значения, для непримитивных типов, таких как объекты или массивы, сравниваются только их ссылки.

Использование:

Ссылки на документацию:

Избегайте экспорта по умолчанию

Экспорт по умолчанию очень быстро становится беспорядочным. В огромных проектах, где реэкспортируется множество объектов, нет возможности поддерживать экспорт по умолчанию.

Используйте индексный файл для повторного экспорта компонентов

В каталоге компонентов используйте индексные файлы, в которые вы повторно экспортируете свой компонент. Это полезно, потому что, когда вы хотите импортировать его, вам не нужно писать

import { MyComponent } from './components/MyComponent/MyComponent';

но достаточно написать

import { MyComponent } from './components/MyComponent';

Ожидаемая структура

Я собираюсь описать каждый каталог с примерами. Вы можете найти всю структуру вместе в конце статьи.

Внутри примеров жирным шрифтом обозначен наш специальный каталог или файл. Другие файлы или каталоги примеров заключены в квадратные скобки.

1. Ядро

src
|__core
   |__[contextConfig].ts
   |__[httpClient].ts
   |__[...]

Включает файлы конфигурации для сторонних библиотек. Это может быть конфигурация вашего http-клиента, может быть, Axios, или настройка тестовой среды, такой как Jest.

2. Общие компоненты

src
|__sharedComponents
   |__[header]
   |  |__[Menu.tsx]
   |  |__[UserPanel.tsx]
   |  |__[Header.tsx]
   |  |__index.ts -> re-exports needed components
   |  |__[...]
   |
   |__[form
   |  |__[Button.tsx]
   |  |__[Form.tsx]
   |  |__[Input.tsx]
   |  |__index.ts -> re-exports needed components
   |  |__[...]
   |
   |__[Sidebar.tsx]
   |__[...]

Как следует из названия, это место для ваших общих компонентов, совместно используемых вашими страницами. Помните, что здесь разрешены только презентационные компоненты. Те, у кого нет побочных эффектов или огромного внутреннего состояния. Когда я говорю «огромное внутреннее состояние», я имею в виду данные из серверной части или так далее. Если вы сохраните состояние окна выбора, если он открыт или закрыт, все в порядке. Вот почему я не говорю о компонентах без состояния.

3. Главный index.tsx

src
|__index.tsx

Как это принято в index.tsx, вы должны визуализировать свой основной компонент в DOM.

3. Страницы

src
|__pages
   |__[login]
   |  |__store
   |  |  |__reducers.ts
   |  |  |__actions.ts
   |  |  |__selectors.ts
   |  |  |__effects.ts
   |  |  |__types.ts
   |  |
   |  |__queries
   |  |  |__[withUser.ts]
   |  |  |__[withUserType.ts]
   |  |  |__[...]
   |  |
   |  |__context
   |  |  |__[withUser.ts
   |  |  |__[withUserType.ts
   |  |  |__[...]
   |  |
   |  |__utils
   |  |  |__[emailValidator.ts
   |  |  |__[...]
   |  |
   |  |__constants
   |  |  |__[userTypes.ts
   |  |  |__[...]
   |  |
   |  |__services
   |  |  |__[loginService].ts
   |  |  |__[...]
   |  |
   |  |__[Login].tsx
   |  |__[Box].tsx
   |  |__index.ts
   |  |__[...]
   |
   |__[products]
   |  |__[list]
   |  |  |__[helper directories]
   |  |  |__[state management directories]
   |  |  |__[List].tsx
   |  |  |__[Item].tsx
   |  |  |__[BaseInfo].tsx
   |  |  |__index.ts
   |  |  |__[...]
   |  |
   |  |__[detail]
   |  |  |__[helper directories]
   |  |  |__[state management directories]
   |  |  |__[component directories/files]
   |  |
   |  |__[...]
   |
   |__routes.tsx
   |__[...]
  • этот каталог представляет страницы вашего веб-приложения, например страницы / продукты или страницы / продукты / список
  • каждый подкаталог имеет свои собственные компоненты реакции, которые используются только на этой странице
  • они независимы друг от друга, поэтому, если вам нужно отредактировать страницу пользователей, изменения будут внесены только в дерево каталогов [users]
  • совместное использование компонентов может осуществляться между двумя страницами с помощью каталога sharedComponents.
  • routes.tsx - это место для основного маршрутизатора в вашем приложении, это точка входа на ваши страницы.

3.1. Справочники государственного управления

В следующих примерах в основном используются компоненты более высокого порядка. Если вам больше нравятся хуки React, вы можете иметь ту же структуру, но с кастомными хуками.

Единственное правило - каждая страница должна иметь собственное независимое состояние.

[хранить]

store
  |__reducers.ts
  |__actions.ts
  |__selectors.ts
  |__effects.ts
  |__types.ts

Мы назвали его store, потому что используемая библиотека для управления магазином может отличаться от проекта к проекту. Чтобы дать вам пример, я собираюсь описать структуру Redux.

actions.ts

  • содержит чистые функции, которые получают данные, подготовленные для сохранения в магазине как есть
  • здесь изменение данных не производится

effects.ts

  • содержат саги, функции преобразования или наблюдаемые, зависит от того, какую библиотеку вы будете использовать для побочных эффектов - если вы их не используете, в этом файле нет необходимости
  • вся подготовка данных для магазина (действия) происходит здесь

reducers.ts

  • содержит структуру магазина с редуктором
  • получает подготовленные данные для сохранения
  • здесь изменение данных не производится

selectors.ts

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

types.ts

  • содержит только определение констант, которое представляет типы действий

В этом случае, если вам не нравятся общие имена, как в приведенных выше примерах, вы можете добавить к нему конкретные имена, например userSelectors.ts, userReducers.ts и т. д.

[запросы]

queries
  |__[withUser].ts
  |__[withUserType].ts
  |__[...]

Для тех, кто использует graphQL, это каталог для запросов и изменений.

  • with [QueryName] .ts - HOC для запроса
  • with [QueryName] Mutation.ts - HOC для мутации

В этом примере я использую react-apollo.

[контекст]

context
  |__[withUser].ts
  |__[withUserType].ts
  |__[...]

Если вам лучше использовать React Context API, это место, где вы можете разместить свои файлы.

  • с [HocName] .tsx

Пример HOC:

После этого вы можете использовать следующие вспомогательные функции.

  • withProviders () - объединяет ваш компонент с поставщиками контекста.
  • withConsumers () - отображает все значения от необходимых потребителей на свойства ваших компонентов.

3.2. Справочники помощников

[login]
  |__utils
  |  |__[emailValidator].ts
  |  |__[...]
  |
  |__constants
  |  |__[userTypes].ts
  |  |__[...]
  |
  |__services
     |__[loginService].ts
     |__[...]

константы

  • здесь вы должны поместить все константы и перечисления, которые вы используете
  • файлы должны иметь описательные имена, например [loginPageUrls] .ts
  • не помещайте все свои константы в один файл, разбивайте их

утилиты

  • включает функции / классы, разделенные на файлы и каталоги
  • [utilName | utilGroupName] .ts - представляет одну функцию или группу функций.
  • например myValidator.ts
  • например, валидаторы / myValidator.ts

Сервисы

  • в основном вызовы API, базовые HTTP-запросы (с использованием axios, fetch и т. д.)
  • services / [serviceName] .ts

В следующем примере показано, как можно настроить axios. Он также включает инициализацию токена доступа из файла cookie браузера.

Пример определения пользовательской службы:

3.3. Компоненты React

[products]
   |__[Login].tsx
   |__[Box].tsx
   |__[ComponentComposedWithSmallComponents]
   |  |__[Component1].tsx
   |  |__[Component2].tsx
   |  |__[Component3].tsx
   |  |__index.ts
   |
   |__[...]

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

Все вместе

src
|__core
|  |__context.ts
|  |__httpClient.ts
|  |__[...]
|
|__sharedComponents
|  |__header
|  |  |__Menu.tsx
|  |  |__UserPanel.tsx
|  |  |__Header.tsx
|  |  |__index.ts
|  |  |__[...]
|  |
|  |__form
|  |  |__Button.tsx
|  |  |__Form.tsx
|  |  |__Input.tsx
|  |  |__index.ts
|  |  |__[...]
|  |
|  |__Sidebar.tsx
|  |__[...]
|
|__pages
|  |__login
|  |  |__store
|  |  |  |__reducers.ts
|  |  |  |__actions.ts
|  |  |  |__selectors.ts
|  |  |  |__effects.ts
|  |  |  |__types.ts
|  |  |
|  |  |__queries
|  |  |  |__withUser.ts
|  |  |  |__withUserType.ts
|  |  |  |__[...]
|  |  |
|  |  |__context
|  |  |  |__withUser.ts
|  |  |  |__withUserType.ts
|  |  |  |__[...]
|  |  |
|  |  |__utils
|  |  |  |__emailValidator.ts
|  |  |  |__[...]
|  |  |
|  |  |__constants
|  |  |  |__userTypes.ts
|  |  |  |__[...]
|  |  |
|  |  |__services
|  |  |  |__loginService.ts
|  |  |  |__[...]
|  |  |
|  |  |__Login.tsx -> <Login />
|  |  |__Box.tsx -> <LoginBox />
|  |  |__index.ts -> re-exports the specific page component
|  |  |__[...]
|  |
|  |__products
|  |  |__list
|  |  |  |__[helper directories]
|  |  |  |__[state management directories]
|  |  |  |__List.tsx -> <ProductsList />
|  |  |  |__Item.tsx -> <ProductsItem />
|  |  |  |__BaseInfo.tsx -> <ProductsBaseInfo />
|  |  |  |__index.ts -> re-exports the specific page component
|  |  |  |__[...]
|  |  |
|  |  |__detail
|  |     |__[helper directories]
|  |     |__[state management directories]
|  |     |__[component directories/files]
|  |     |__[...]
|  |
|  |__routes.tsx
|  |
|__index.ts

Большое спасибо за чтение!