React Idiomatic File Structure
Данная концепция представляет собой набор сущностей и приёмов, позволяющих достичь максимальной простоты в работе с файловой структурой, подкреплённой принципами интуитивности, расширяемости и модульности.
Основная проблема, решаемая RIFS, - нивелирование человеческого
индивидуального фактора в работе с React
-приложением. Многое нижеизложенное
позаимствовано из фреймворка AngularJS
, перенося некоторые подходы на реалии
работы в экосистеме React
.
Целью этого проекта является достижение чувства "until it feels right".
Для достижения цели будет использовано разделение на узлы (nodes), сущности (entities), типы (types) и свойства (features). Каждый элемент должен следовать принципу единой ответственности.
Принцип наименования всегда един - использование т.н. kebab-case
(иногда
dash-case
) с разделением через точку. Для примера возьмём
list-item.component.tsx
. В данном случае list-item
является названием узла,
component
- типом узла, tsx
- расширением (отображающим используемый в
данном узле язык).
Узел (node) - файл, папка, или группа файлов/папок, собираемые в единое свойство приложения. Узел несёт ответственность за свою часть функциональности и может быть частью других узлов, составляя с ними более сложные структуры.
В файловой системе узел выражается через корневую часть названия файла или папки.
Сущность (entity) - узел, представленный в виде файла. Является ключевым в своей папке и может представлять одну из приведённых моделей:
- Компонéнт (component)
- Контéкст (context)
- Сéрвис (service)
Все сущности тем или иным образом взаимодействуют с API React
, имея свою
задачу и зону ответственности.
Компонентом является сущность, работающая с DOM
-деревом и напрямую
взаимодействующая с пользователем в виде интерфейса (view). Компонент может
иметь собственное состояние (state), выражаемое через хуки, и получать
зависимости через пропсы (props) или через контекст.
export const ListItem: FC<ListItemProps> = ({ index }) => {
/* любая логика и состояние
возврат DOM элементов */
};
Контекст отвечает за общую логику сродных узлов и выступает поставщиком (provider) пропсов. С помощью контекста достигается принцип инверсии управления, благодаря которому нижестоящие узлы получают доступ к зависимостям извне, что позволяет лучше контролировать поток данных и тестировать эти узлы.
Контекст включает в себя две составные части - поставщик и хук. Поставщик должен оборачиваться над всеми заинтересованными узлами снаружи, давая им доступ к данным. Хук забирает на себя роль получателя (consumer), подписывающего узел на изменения состояния.
const PathContext = createContext<PathModel | undefined>();
const Path: FC = ({ children }) => {
return (
<PathContext.Provider
value={
{
/* */
}
}
>
{children}
</PathContext.Provider>
);
};
export const usePath = (): PathModel => {
const context = useContext(Path);
if (context === undefined) {
throw new ReferenceError('Use Path inside of its provider.');
}
return context;
};
export default Path;
Сервис является частным случаем контекста. Основным отличием является то, что сервис должен быть инстанциирован единожды. Например, провайдер темы приложения.
Наиболее частой областью применения сервиса является выделение единого куска логики, поставляемого в необходимые места.
Чем уже область применения сервиса - тем лучше.
Тип узла более узко определяет его назначение. Сущности также являются и типами,
собирающими в себе другие типы. Также, узел может не иметь явно заданного типа,
что относится к, например, утилитным функциям или хукам. Их тип подразумевается
из места их расположения (папки utils/
или hooks/
).
К типам относятся:
- Стили (
styles
) - Пропсы (
props
) - Конфигурация (
config
) - Модуль (
module
) - Тесты (
spec
)
list-item.styles.scss
Стили напрямую относятся к компонентам и являются источником стилизации для них.
list-item.props.d.ts
Для описания пропсов компонента используется специальный тип props
.
Всё, что к пропсам не относится, должно быть выделено в отдельный узел entity
,
который должен собирать в себе модели и интерфейсы, дополняющие модель
компонента.
requests.config.ts
В конфигах собраны данные, необходимые в настройках работы узлов (чаще всего приложения). К таким, например, относятся конфиги запросов или отдельных сервисов.
routes.module.ts
Модулем может являться выделенный группа объектов или объект, содержащий в себе статические данные, и который не зависит от происходящих вокруг него процессов. Например, модулем может являться файл, собирающий в себе массив роутов и отдающий ответственность за их поведение и расположение компоненту.
list-item.spec.ts
Каждый узел, в идеале, должен быть покрыт unit-тестами, вынесенными в файл с
типом spec
.
Свойство (feature ) - высокоуровневый самостооятельный блок, из которого собирается всё приложение. В него входят все вышеописанные узлы, которые также могут являться свойствами.
В приложении обязательно должно быть core
-свойство, который содержит в себе
компонент приложения app
и дополнительные глобальные свойства, такие как
theme
или store
.
Также, в приложении может присутствовать shared
-свойство, собирающее в себе
все общие и переиспользуемые узлы и типы.
Отдельным блоком стоит папка assets
, поставляющая в приложение и пользователю
все, не связанные с функциональностью элементы. Например, assets/images
,
assets/fonts
.
Основным свойством приложения являются его модули. В данном контексте модули представляют собой основные строительные блоки приложения.
Говоря о React
-приложении, под модулями мы подразумеваем его страницы. Каждый
модуль (страница), как и сервис, должен иметь единый инстанс и являться
родителем для всех нижестоящих компонентов.
Модуль также должен получать свои зависимости извне, для чего все его сервисы
должны быть инстанциированы в компоненте app
, который также принимает на себя
функцию Service Locator
.
Каждая папка (кроме корневых узлов внутри src
) должна иметь файл index.ts
,
экспортирующий все необходимые узлы из неё. В контексте экосистемы NodeJS
index.ts
является корневым модулем любого узла, через который происходит
взаимодействие узлов друг с другом.
В итоге, наше приложение может иметь следующую структуру файлов:
src/
assets/
images/
icons/
fonts/
styles/
core/
app/
theme/
store/
routes.module.ts
modules/
(pages/
)main/
about/
contacts/
form/
form-validator/
form-validator.service.tsx
form-validator.spec.tsx
index.ts
form.component.tsx
form.styles.ts
index.ts
about.component.tsx
about.styles.tsx
index.ts
shared/
components/
entity/
services/
utils/
hooks/