Миграция с кастомной архитектуры
Это руководство описывает подход, который может быть полезен при миграции с кастомной самодельной архитектуры на Feature-Sliced Design.
Вот структура папок типичной кастомной архитектуры. Мы будем использовать ее в качестве примера в этом руководстве. Нажмите на синюю стрелку, чтобы открыть папку.
📁 src
📁 actions
- 📁 product
- 📁 order
- 📁 api
- 📁 components
- 📁 containers
- 📁 constants
- 📁 i18n
- 📁 modules
- 📁 helpers
📁 routes
- 📁 products.jsx
- 📄 products.[id].jsx
- 📁 utils
- 📁 reducers
- 📁 selectors
- 📁 styles
- 📄 App.jsx
- 📄 index.js
Перед началом
Самый важный вопрос, который нужно задать своей команде при рассмотрении перехода на Feature-Sliced Design, — действительно ли вам это нужно? Мы любим Feature-Sliced Design, но даже мы признаем, что некоторые проекты прекрасно обойдутся и без него.
Вот несколько причин, по которым стоит рассмотреть переход:
- Новые члены команды жалуются, что сложно достичь продуктивного уровня
- Внесение изменений в одну часть кода часто приводит к тому, что ломается другая несвязанная часть
- Добавление новой функциональности затруднено из-за огромного количества вещей, о которых нужно думать
Избегайте перехода на FSD против воли ваших коллег, даже если вы являетесь тимлидом.
Сначала убедите своих коллег в том, что преимущества перевешивают стоимость миграции и стоимость изучения новой архитектуры вместо установленной.
Также имейте в виду, что любые изменения в архитектуре незаметны для руководства в моменте. Убедитесь, что они поддерживают переход, прежде чем начинать, и объясните им, как этот переход может быть полезен для проекта.
Если вам нужна помощь в убеждении менеджера проекта в том, что FSD вам полезен, вот несколько идей:
- Миграция на FSD может происходить постепенно, поэтому она не остановит разработку новых функций
- Хорошая архитектура может значительно сократить время, которое потребуется новым разработчикам для достижения производительности
- FSD — это документированная архитектура, поэтому команде не нужно постоянно тратить время на поддержание собственной документации
Если вы всё-таки приняли решение начать миграцию, то первое, что вам следует сделать, — настроить алиас для 📁 src
. Это будет полезно позже, чтоб ссылаться на папки верхнего уровня. Далее в тексте мы будем считать @
псевдонимом для ./src
.
Шаг 1. Разделите код по страницам
Большинство кастомных архитектур уже имеют разделение по страницам, независимо от размера логики. Если у вас уже есть 📁 pages
, вы можете пропустить этот шаг.
Если у вас есть только 📁 routes
, создайте 📁 pages
и попробуйте переместить как можно больше кода компонентов из 📁 routes
. Идеально, если у вас будет маленький файл роута и больший файл страницы. При перемещении кода создайте папку для каждой страницы и добавьте в нее индекс-файл:
Пока что ваши страницы могут импортировать друг из друга, это нормально. Позже будет отдельный шаг для устранения этих зависимостей, но сейчас сосредоточьтесь на установлении явного разделения по страницам.
Файл роута:
export { ProductPage as default } from "@/pages/product"
Индекс-файл страницы:
export { ProductPage } from "./ProductPage.jsx"
Файл с компонентом страницы:
export function ProductPage(props) {
return <div />;
}
Шаг 2. Отделите все остальное от страниц
Создайте папку 📁 src/shared
и переместите туда все, что не импортируется из 📁 pages
или 📁 routes
. Создайте папку 📁 src/app
и переместите туда все, что импортирует страницы или роуты, включая сами роуты.
Помните, что у слоя Shared нет слайсов, поэтому сегменты могут импортировать друг из друга.
В итоге у вас должна получиться структура файлов, похожая на эту:
📁 src
📁 app
📁 routes
- 📄 products.jsx
- 📄 products.[id].jsx
- 📄 App.jsx
- 📄 index.js
📁 pages
📁 product
📁 ui
- 📄 ProductPage.jsx
- 📄 index.js
- 📁 catalog
📁 shared
- 📁 actions
- 📁 api
- 📁 components
- 📁 containers
- 📁 constants
- 📁 i18n
- 📁 modules
- 📁 helpers
- 📁 utils
- 📁 reducers
- 📁 selectors
- 📁 styles
Шаг 3. Устраните кросс-импорты между страницами
Найдите все случаи, когда одна страница импортирует что-то из другой, и сделайте одно из двух:
- Скопируйте код, который импортируется, в зависимую страницу, чтобы убрать зависимость
- Переместите код в соответствующий сегмент в Shared:
- если это часть UI-кита, переместите в
📁 shared/ui
; - если это константа конфигурации, переместите в
📁 shared/config
; - если это взаимодействие с бэкендом, переместите в
📁 shared/api
.
Копирование само по себе не является архитектурной проблемой, на самом деле иногда даже правильнее продублировать что-то, чем абстрагировать в новый переиспользуемый модуль. Дело в том, что иногда общие части страниц начинают расходиться, и в этих случаях вам не нужно, чтобы эти зависимости мешались.
Однако существует смысл в принципе DRY ("don't repeat yourself" — "не повторяйтесь"), поэтому убедитесь, что вы не копируете бизнес-логику. В противном случае вам придется держать в голове, что баги нужно исправлять в нескольких местах одновременно.
Шаг 4. Разберите слой Shared
На данном этапе у вас может быть много всего в слое Shared, и в целом, следует избегать таких ситуаций. Причина этому в том, что слой Shared может быть зависимостью для любого другого слоя в вашем коде, поэтому внесение изменений в этот код автоматически более чревато непредвиденными последствиями.
Найдите все объекты, которые используются только на одной странице, и переместите их в слайс этой страницы. И да, это относится и к экшнам (actions), редьюсерам (reducers) и селекторам (selectors). Нет никакой пользы в группировке всех экшнов вместе, но есть польза в том, чтобы поместить актуальные экшны рядом с их местом использования.
В итоге у вас должна получиться структура файлов, похожая на эту:
📁 src
- 📁 app (unchanged)
📁 pages
📁 product
- 📁 actions
- 📁 reducers
- 📁 selectors
📁 ui
- 📄 Component.jsx
- 📄 Container.jsx
- 📄 ProductPage.jsx
- 📄 index.js
- 📁 catalog
📁 shared (only objects that are reused)
- 📁 actions
- 📁 api
- 📁 components
- 📁 containers
- 📁 constants
- 📁 i18n
- 📁 modules
- 📁 helpers
- 📁 utils
- 📁 reducers
- 📁 selectors
- 📁 styles
Шаг 5. Распределите код по техническому назначению
В FSD разделение по техническому назначению происходит с помощью сегментов. Существует несколько часто встречающихся сегментов:
ui
— всё, что связано с отображением интерфейса: компоненты UI, форматирование дат, стили и т. д.api
— взаимодействие с бэкендом: функции запросов, типы данных, мапперы и т. д.model
— модель данных: схемы, интерфейсы, хранилища и бизнес-логика.lib
— библиотечный код, который нужен другим модулям на этом слайсе.config
— файлы конфигурации и фиче-флаги.
Вы можете создавать свои собственные сегменты, если это необходимо. Убедитесь, что не создаете сегменты, которые группируют код по тому, чем он является, например, components
, actions
, types
, utils
. Вместо этого группируйте код по тому, для чего он предназначен.
Перераспределите код ваших страниц по сегментам. У вас уже должен быть сегмент ui
, теперь пришло время создать другие сегменты, например, model
для ваших экшнов, редьюсеров и селекторов, или api
для ваших thunk-ов и мутаций.
Также перераспределите слой Shared, чтобы удалить следующие папки:
📁 components
,📁 containers
— большинство из их содержимого должно стать📁 shared/ui
;📁 helpers
,📁 utils
— если остались какие-то повторно используемые хелперы, сгруппируйте их по назначению, например, даты или преобразования типов, и переместите эти группы в📁 shared/lib
;📁 constants
— так же сгруппируйте по назначению и переместите в📁 shared/config
.
Шаги по желанию
Шаг 6. Создайте сущности/фичи ёмкостью из Redux-слайсов, которые используются на нескольких страницах
Обычно эти переиспользуемые Redux-слайсы будут описывать что-то, что имеет отношение к бизнесу, например, продукты или пользователи, поэтому их можно переместить в слой Entities, одна сущность на одну папку. Если Redux-слайс скорее связан с действием, которое ваши пользователи хотят совершить в вашем приложении, например, комментарии, то его можно переместить в слой Features.
Сущности и фичи должны быть независимы друг от друга. Если ваша бизнес-область содержит встроенные связи между сущностями, обратитесь к руководству по бизнес-сущностям за советом по организации этих связей.
API-функции, связанные с этими слайсами, могут остаться в 📁 shared/api
.
Шаг 7. Проведите рефакторинг modules
Папка 📁 modules
обычно используется для бизнес-логики, поэтому она уже довольно похожа по своей природе на слой Features из FSD. Некоторые модули могут также описывать большие части пользовательского интерфейса, например, шапку приложения. В этом случае их можно переместить в слой Widgets.
Шаг 8. Сформируйте чистый фундамент UI в shared/ui
📁 shared/ui
, в идеале, должен содержать набор UI-элементов, в которых нет бизнес-логики. Они также должны быть очень переиспользуемыми.
Проведите рефакторинг UI-компонентов, которые раньше находились в 📁 components
и 📁 containers
, чтобы отделить бизнес-логику. Переместите эту бизнес-логику в верхние слои. Если она не используется в слишком многих местах, вы даже можете рассмотреть копирование как вариант.