Effector и Feature-Sliced Design (FSD) — сочетание давно проверенное в продакшене. Наш frontend-разработчик Антон плотно использовал эту связку на больших проектах и увидел сильные и слабые стороны этой архитектуры. В статье разберем, как устроен FSD, как в него встраивается Effector и какие фундаментальные проблемы есть у этого дуэта.
Feature-Sliced Design (FSD)
FSD помогает избежать проблем простой модульной архитектуры. Эта методология делит код на слои и модули (слайсы). Каждый слой отвечает за определенную зону ответственности, а слайсы внутри слоев — за конкретную задачу или бизнес-сущность. Так мы добиваемся однонаправленного потока данных и высокой связности кода.
Три базовых понятия
- Слой — уровень иерархии с четкой бизнес-направленностью (shared, entities, features, widgets, pages, processes, app). Часть слоев опциональна.
- Слайс — модуль внутри слоя, объединяющий логику, UI, запросы и константы в одном месте.
- Сегменты — папки внутри каждого слайса: UI, model, lib, API, config и constants.
Как работают слои
- Shared хранит все общие компоненты без привязки к бизнес-логике: кнопки, инпуты, хелперы, конфиги, глобальные стили.
- Entities — сущности предметной области: товар, заказ, пользователь и т.д. Внутри — компоненты и функции, применимые только к этой сущности.
- Features — самостоятельные бизнес-функциональности (лайк, комментарий, авторизация, подписка). Используют сущности и shared.
- Widgets — крупные видимые блоки, которые внутри себя могут включать фичи (например, верхняя панель, карточка поста).
- Pages — собирают виджеты и фичи в одну страницу. Тут минимум логики, в основном набор виджетов и их макет.
- Processes — для многоэтапных операций, охватывающих несколько страниц (необязательный слой).
- App — точка входа, инициализация приложения: роутер, провайдеры, глобальная конфигурация.
Принцип работы
- Поток данных однонаправленный: нижние слои не используют верхние. Фича не может брать код из виджета, а entity не лезет в page.
- Каждый модуль (слайс) имеет публичный API. Внешнему коду доступны только явно экспортированные сущности, внутренняя реализация скрыта. Это повышает изоляцию и упрощает поддержку.
- Модули легко заменять или удалять. Если виджет не нужен, его убираем без опасности «сломать» соседние блоки.
Практические советы по внедрению
- Внедряйте FSD пошагово: определите слой App, создавайте модули в Entities и Features, храните общие элементы в Shared.
- При необходимости можно упорядочить слои префиксами (01_pages, 02_entities), но лучше воспользоваться алиасами в настройках, чтобы упростить переход от префиксов к более «чистым» названиям.
- По мере роста проекта строго следите, чтобы слайсы не тянулись к слоям выше.
- В Shared складывают общую инфраструктуру и переиспользуемые компоненты, где нет бизнес-логики.
- Если сложно ориентироваться в папках, некоторые добавляют числовые префиксы (например, 01-pages, 02-widgets). Но лучше использовать алиасы и настроить импорты так, чтобы при отказе от префиксов не пришлось переписывать весь код.
Что такое Effector
Effector — это инструмент для управления состоянием в JavaScript-приложениях. Он помогает описывать бизнес-логику в виде небольших, независимых модулей и включает три базовых концепции.
- Store. Хранит состояние и обновляется при наступлении событий. Можно создавать сколько угодно store, и каждый отвечает за свой участок данных.
- Event. Функция, отображающая изменение или действие в системе. На события можно подписываться, чтобы обновлять состояние store или запускать другие процессы.
- Effect. Специализированное событие для асинхронных операций: запросов к серверу, таймеров или работы с кэшем. Выполняет «побочные» действия и возвращает результат для дальнейшей обработки.
Кроме них в Effector есть вспомогательные функции (sample и т. д.), которые позволяют связывать события, сторы и эффекты в гибкую логику приложения без избыточного кода.
Обычно эффекты (createEffect) применяют для работы с API, запросов к серверу и любой асинхронной логики. При необходимости их можно расширять или заменять другими инструментами для сетевых операций. Effector не привязан к конкретному фреймворку: его можно применять с React, Vue или любым другим.
Официальная документация помогает разобраться с API, паттернами и рекомендациями по организации кода.
Таким образом Effector дает простую модель, где события изменяют состояние в store, а асинхронные операции оформляются как эффекты, без громоздкого бойлерплейта.
Достоинства Effector
- Небольшой вес. Библиотека компактна, поэтому приложение не обрастает лишним кодом.
- Быстрый вход. Для базовой работы достаточно понимать три единицы (Store, Event, Effect) и функцию sample. Этого хватит, чтобы собирать рабочую логику и объяснить новый код коллегам.
- Развитая экосистема. Уже есть готовые утилиты, роутинг, web API, кеш-запросы (query), средства для форм, SSR и многое другое. Это ускоряет написание кода и облегчает поддержку.
- Framework-агностичен. Effector не привязан к React или Vue, его можно внедрять в любую среду и комбинировать с другими инструментами.
- Batching вычислений. Effector группирует обновления, избегая лишних перерендеров и улучшая производительность.
- Граф связей вместо дерева. Вместо одного большого стора — набор атомарных сторов, связанных в граф. Это гибче, дает наглядную визуализацию зависимостей и позволяет точечно контролировать логику.
- Истинная MVC-модель. Логика полностью отделена от жизненного цикла компонента. Сайд-эффекты (запросы, хранение данных) остаются за рамками UI, поэтому UI проще переиспользовать.
- Контроль над рендерами. За счет независимости от React-специфики в коде меньше мемоизации (useCallback, useMemo), а перерендеры сокращаются за счет отслеживания конкретных сторов и событий.
- Отличная поддержка SSR. Effector легко адаптируется под серверный рендеринг (есть готовые решения), в том числе благодаря созданию отдельных «доменов» состояния и их «склеиванию» при переходе с сервера на клиент.
Проблемы Effector
Избыточная декларативность
Effector требует множество объявлений (stores, events, effects) и связок через sample. Код может разрастаться и занимать в несколько раз больше строчек по сравнению с простой функцией.
Effector требует множество объявлений (stores, events, effects) и связок через sample. Код может разрастаться и занимать в несколько раз больше строчек по сравнению с простой функцией.
Разрозненность логики
Связи через sample могут располагаться в разных местах проекта. Если не следовать четким правилам (код-стилю, соглашениям по расположению) — читать и поддерживать код сложно.
Связи через sample могут располагаться в разных местах проекта. Если не следовать четким правилам (код-стилю, соглашениям по расположению) — читать и поддерживать код сложно.
Неочевидная последовательность вызовов
Внутренние механизмы Effector (приоритет подписок .on над sample) могут привести к путанице в том, какой код и когда срабатывает первым.
Внутренние механизмы Effector (приоритет подписок .on над sample) могут привести к путанице в том, какой код и когда срабатывает первым.
Особый «домен» внутри JavaScript
Effector вынуждает использовать собственные операторы (sample, combine и т.п.) или сторонние библиотеки (Patronum) вместо привычных if/else или прямых функций. Это увеличивает порог вхождения и требует от разработчиков дополнительной гибкости.
Effector вынуждает использовать собственные операторы (sample, combine и т.п.) или сторонние библиотеки (Patronum) вместо привычных if/else или прямых функций. Это увеличивает порог вхождения и требует от разработчиков дополнительной гибкости.
Необходимость строгой архитектуры и код-стиля
Чтобы реакции и бизнес-логика не превращались в хаотичный набор сэмплов, команде нужно договориться, где хранить юниты (store, event, effect) и как их именовать. Иначе логику сложно масштабировать.
Чтобы реакции и бизнес-логика не превращались в хаотичный набор сэмплов, команде нужно договориться, где хранить юниты (store, event, effect) и как их именовать. Иначе логику сложно масштабировать.
Как уменьшить хаос в коде Effector
Единый код-стайл и разделение на «домены»
- Старайтесь группировать store, event и effect в одном месте, не разбрасывайте их по разным файлам.
- Соблюдайте внутренний порядок: сначала объявление юнитов, потом их подписки и связывание через sample, и только затем экспорты.
Фабрики
- Выносите типовые паттерны в утилиты (например, «фабрику для модалок», «фабрику для булевых сторов»).
- Это сокращает объем кода и избавляет от повторяющихся объявлений.
Effector Models
Разработчики Effector планируют добавить «модели» как дополнительный способ структурировать логику. Это должно упростить создание и связку сторов.
Разработчики Effector планируют добавить «модели» как дополнительный способ структурировать логику. Это должно упростить создание и связку сторов.
Добавьте императивности через createAction
В моделях на Effector логика часто расползается по десяткам sample. Это делает код слабо связанным и трудным для чтения и ревью. Структура действий теряется, порядок срабатывания становится критичным, появляются race condition и неявные условия. В итоге, даже несложная модель может превратиться в хрупкий механизм.
В чем проблема sample?
Когда в модели все завязано на sample, глобальная логика распадается на куски. Каждый отвечает за маленькую часть — реакцию на событие, проверку условия, вызов эффекта. Такие куски сложно связать в уме: приходится ментально парсить модель, выяснять, что к чему относится и в каком порядке вызывается.
При этом:
- Логика одного действия может быть раскидана по разным местам.
- Условия легко теряют консистентность при перестановке семплов.
- Ошибки появляются не из-за сложности задачи, а из-за того, что она размазана по модели.
Как помогает createAction
createAction позволяет упаковать всю логику одного действия в одном месте. Это дает:
- Именованность. Появляется точка входа — понятно, где искать и что менять.
- Связанность. Вся логика действия рядом — меньше прыжков по коду.
- Линейность. Код читается сверху вниз, а не как граф из sample.
Результат — более императивный стиль, в котором проще думать, отлаживать и вносить изменения. Весь флоу — в одном месте. Легко читать, ревьюить, дебажить. Ошибки из-за неправильного порядка вызовов исключены.
Итоги
Связка Effector и Feature-Sliced Design дает гибкость, но требует внимательного отношения к архитектуре. Если правильно выстроить структуру, получится модульный и управляемый проект с отделенной логикой и понятным потоком данных. Но все это работает только при условии, что команда (или хотя бы ты сам) соблюдает договоренности.
Без четкого кодстайла, структуры и дисциплины Effector быстро превращается в мешанину из sample, store, event и effect. В моем опыте больше всего помогли фабрики, createAction и разбиение проекта на домены. Это снижает уровень шума, делает код более линейным и заметно упрощает поддержку. Надо понимать, что Effector — не универсальное решение. Но если хочется контролировать логику на уровне бизнес-событий, а не UI-компонентов — это по-прежнему один из лучших инструментов, особенно в связке с FSD.