Основы

Проект на FSD состоит из слоев (layers), каждый слой состоит из слайсов (slices) и каждый слайс состоит из сегментов (segments).

Слои стандартизированы во всех проектах и расположены вертикально. Модули на одном слое могут взаимодействовать лишь с модулями, находящимися на слоях строго ниже. На данный момент слоев семь (снизу вверх):

shared

  1. shared — переиспользуемый код, не имеющий отношения к специфике приложения/бизнеса. Переиспользуемые модули, без привязки к бизнес-логике. (например, UIKit, lib (хуки и функции хелперы), api (общие настройки запросов), assets, config (конфигурации сторибука и другое), const, types)

  2. entities (Опц. сущности) — бизнес-сущности, которыми оперирует предметная область (например, User, Product, Order)

  3. features (Опц. фичи) — взаимодействия с пользователем, действия, которые несут бизнес-ценность для пользователя. (например, SendComment, AddToCart, UsersSearch)

  4. widgets (виджеты) — композиционный слой для соединения сущностей и фич в самостоятельные блоки (например, IssuesList, UserProfile).

  5. pages (страницы) — композиционный слой для сборки полноценных страниц из сущностей, фич и виджетов.

  6. processes (Опц. процессы, устаревший слой) — сложные сценарии, покрывающие несколько страниц. (например, авторизация)

  7. app — настройки, стили и провайдеры для всего приложения.

Затем есть слайсы, разделяющие код по предметной области. Они группируют логически связанные модули, что облегчает навигацию по кодовой базе. Слайсы не могут использовать другие слайсы на том же слое, что обеспечивает высокий уровень связности (cohesion) при низком уровне зацепления (coupling).

В свою очередь, каждый слайс состоит из сегментов. Это маленькие модули, главная задача которых — разделить код внутри слайса по техническому назначению. Самые распространенные сегменты — uimodel (store, actions), api и lib (utils/hooks), но в вашем слайсе может не быть каких-то сегментов, могут быть другие, по вашему усмотрению.

В большинстве случаев рекомендуется располагать api и config только в shared-слое

Практический пример

Подготавливаем структуру

Цель данного туториала - показать другой взгляд на привычные практики при проектировании

Адаптируем структуру к нужному виду

Подключаем поддержку абсолютных импортов для удобства

Вот, как это поможет нам в будущем

Как можно заметить - мы перенесли всю базовую логику в директорию app/

Именно там, согласно методологии, стоит располагать всю подготовительную логику:

  • подключение глобальных стилей (/app/styles/** + /app/index.css)
  • провайдеры и HOCs с инициализирующей логикой (/app/providers/**)

Пока что перенесем туда всю существующую логику, а другие директории оставим пустыми, как на схеме выше.

Подключаем глобальные стили

Установим зависимости:#SaSS

Заводим файлы для стилей:#normalize

Подключаем все стили:

Добавим роутинг

Установим зависимости:#react-router#react-router-dom#compose-function Добавим#HOC для инициализации роутера.

import { Suspense } from "react";
import { BrowserRouter } from "react-router-dom";
 
export const withRouter = (component: () => React.ReactNode) => () => (    
	<BrowserRouter>        
		<Suspense fallback="Loading...">            
			{component()}        
		</Suspense>    
	</BrowserRouter>
	);
import compose from "compose-function";
import { withRouter } from "./with-router";
 
export const withProviders = compose(withRouter);
 
import { withProviders } from "./providers";
	...
	const App = () => {...}
	
	export default withProviders(App);

Это лишь одна из реализаций роутинга

  • Можно объявлять его декларативно либо через список роутов (+ react-router-config)
  • Можно объявлять его на уровне pages либо app

Методология пока никак не регламентирует реализацию этой логики

Добавим временную страницу для проверки роутинга:

 
const TestPage = () => {    
	return <div>Test Page</div>;
};
 
export default TestPage;
//! Сформироуем роуты
// Либо использовать @loadable/component, в рамках туториала - некритично
 
import { lazy } from "react";
import { Route, Routes, Navigate } from "react-router-dom";
 
const TestPage = lazy(() => import("./test"));
 
export const Routing = () => {    
	return (        
		<Routes>            
			<Route path="/" element={<TestPage/>} />            
			<Route path="*" element={<Navigate to="/" />} />        
		</Routes>    
	);
};
// Подключаем роутинг к приложению
 
import { Routing } from "pages";
 
const App = () => (    
	// Потенциально сюда можно вставить     
	// Единый на все приложение хедер    
	// Либо же делать это на отдельных страницах    
	<Routing />
)
...

Layers: app, pages

Здесь мы использовали сразу несколько слоев:

  • app - для инициализации роутера (HOC: withRouter)
  • pages - для хранения модулей страниц
Подключим#ui-kit

Установим зависимости:#antd

Но вы можете использовать любой другой UIKit или же создать собственный, расположив компоненты в shared/ui - именно там рекомендуется хранить UIKit приложения:

import { Checkbox } from "antd"; // ~ "shared/ui/checkbox"
import { Card } from "antd"; // ~ "shared/ui/card"