Перейти к основному содержимому

Миграция с кастомной архитектуры

Это руководство описывает подход, который может быть полезен при миграции с кастомной самодельной архитектуры на 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, но даже мы признаем, что некоторые проекты прекрасно обойдутся и без него.

Вот несколько причин, по которым стоит рассмотреть переход:

  1. Новые члены команды жалуются, что сложно достичь продуктивного уровня
  2. Внесение изменений в одну часть кода часто приводит к тому, что ломается другая несвязанная часть
  3. Добавление новой функциональности затруднено из-за огромного количества вещей, о которых нужно думать

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

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

подсказка

Если вам нужна помощь в убеждении менеджера проекта в том, что FSD вам полезен, вот несколько идей:

  1. Миграция на FSD может происходить постепенно, поэтому она не остановит разработку новых функций
  2. Хорошая архитектура может значительно сократить время, которое потребуется новым разработчикам для достижения производительности
  3. FSD — это документированная архитектура, поэтому команде не нужно постоянно тратить время на поддержание собственной документации

Если вы всё-таки приняли решение начать миграцию, то первое, что вам следует сделать, — настроить алиас для 📁 src. Это будет полезно позже, чтоб ссылаться на папки верхнего уровня. Далее в тексте мы будем считать @ псевдонимом для ./src.

Шаг 1. Разделите код по страницам

Большинство кастомных архитектур уже имеют разделение по страницам, независимо от размера логики. Если у вас уже есть 📁 pages, вы можете пропустить этот шаг.

Если у вас есть только 📁 routes, создайте 📁 pages и попробуйте переместить как можно больше кода компонентов из 📁 routes. Идеально, если у вас будет маленький файл роута и больший файл страницы. При перемещении кода создайте папку для каждой страницы и добавьте в нее индекс-файл:

примечание

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

Файл роута:

src/routes/products.[id].js
export { ProductPage as default } from "@/pages/product"

Индекс-файл страницы:

src/pages/product/index.js
export { ProductPage } from "./ProductPage.jsx"

Файл с компонентом страницы:

src/pages/product/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. Устраните кросс-импорты между страницами

Найдите все случаи, когда одна страница импортирует что-то из другой, и сделайте одно из двух:

  1. Скопируйте код, который импортируется, в зависимую страницу, чтобы убрать зависимость
  2. Переместите код в соответствующий сегмент в 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, чтобы отделить бизнес-логику. Переместите эту бизнес-логику в верхние слои. Если она не используется в слишком многих местах, вы даже можете рассмотреть копирование как вариант.

See also