Когда вы начинаете кодировать новое приложение, всегда возникает большой вопрос, как его организовать. Я собираюсь описать вам идеальную файловую структуру с лучшими практиками, которые можно использовать практически в любом сценарии и с которыми легко работать.
В чем преимущества структуры проекта?
- Членам команды не нужно спорить, где разместить тот или иной функционал.
- Можно описать, чтобы не приходилось каждый раз объяснять новичкам
- С большим количеством фронтенд-проектов с той же структурой легко поменять местами членов команды.
- Вы можете сосредоточиться на функциональности, а не думать о структуре
Что должна выполнять хорошая структура?
- Легкость
- Гибкость
- Возможность повторного использования
- Повысить продуктивность
Лучшие практики использования компонентов
Именование файлов компонентов
- имя каталога компонентов PascalCase
- имя файла компонента PascalCase
- НЕ повторяйте имя родителя
- ПЛОХО: src / Banners / BannersEdit / BannersEditForm.tsx
- ХОРОШО: src / Banners / Edit / Form.tsx
Именование компонентов
Имя ваших компонентов должно копировать путь к компоненту, например.
- src / pages / Login / index.tsx - ‹Login /›
- src / pages / Login / Form.tsx - ‹LoginForm /›
Это гарантирует, что вы всегда будете иметь уникальные имена для всех компонентов.
Именование компонентов контейнера
Если вы хотите экспортировать обернутый компонент с помощью React.memo () или compose (), добавьте Component суффикс к определению вашего класса и экспортируйте const с именем без суффикса.
ИЗБЕГАЙТЕ создания компонентов с зависимым состоянием на реквизитах - кроме начальных значений
Вам нужно следовать принципу единого источника истины, а это означает, что копирование данных из реквизита в состояние не является решением, потому что реквизиты могут изменяться. Вы можете инициализировать состояние в соответствии с реквизитами, но только в конструкторе. Это неприемлемо для любого другого метода жизненного цикла, потому что вы будете использовать .setState (), который запускает повторную визуализацию.
Компонент React - setState ()
setState () всегда будет приводить к повторному рендерингу, если shouldComponentUpdate () не вернет false
Это означает, что вы должны использовать setState () для изменения состояния компонентов и никогда не изменять состояние напрямую. Также нам нужно знать, что когда мы работаем с составными типами, мы делимся ссылками.
Пример:
Здесь мы напрямую изменяем свойства propertyOne
в состоянии.
Решение без новой библиотеки:
Дополнительная литература:
- https://medium.freecodecamp.org/handling-state-in-react-four-immutable-approaches-to-consider-d1f5c00249d5
- https://itnext.io/updating-properties-of-an-object-in-react-state-af6260d8e9f5
ИЗБЕГАЙТЕ создания новых ссылок в render () - используйте функции-обработчики
Пример - создание нового объекта и анонимной функции и передача их в качестве свойств:
Это приведет к тому, что каждый раз, когда ParentComponent
re-renders, ChildComponent
re-renders также будет.
Почему? Это потому, что в каждом цикле рендеринга ParentComponent
мы создаем новый объект и функцию, и вы передаете эти вновь созданные объекты, поэтому ссылки на свойства всегда меняются.
Как написать функции обработчика событий, чтобы избежать новых ссылок
В случае, когда нам нравится использовать только одно примитивное значение:
В случае, когда нам нравится использовать непримитивные значения (объекты, массивы):
Используйте React.PureComponent и React.memo
Почему? Это потому, что React.PureComponent
и React.memo
по умолчанию реализуют неглубокое сравнение свойств и состояний. React.memo
- это версия React.PureComponent
для функциональных компонентов. Неглубокое сравнение означает, что свойства и состояния (до изменений и после изменений) сравниваются только на 0-м уровне. Для примитивных типов сравниваются их значения, для непримитивных типов, таких как объекты или массивы, сравниваются только их ссылки.
Использование:
Ссылки на документацию:
- React.PureComponent https://reactjs.org/docs/react-api.html#reactpurecomponent
- React.memo https://reactjs.org/docs/react-api.html#reactmemo
Избегайте экспорта по умолчанию
Экспорт по умолчанию очень быстро становится беспорядочным. В огромных проектах, где реэкспортируется множество объектов, нет возможности поддерживать экспорт по умолчанию.
Используйте индексный файл для повторного экспорта компонентов
В каталоге компонентов используйте индексные файлы, в которые вы повторно экспортируете свой компонент. Это полезно, потому что, когда вы хотите импортировать его, вам не нужно писать
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
Большое спасибо за чтение!