Effector — когда команда и фичи выходят на масштаб
Дополнительный модуль. Основной стек курса — TanStack Query (серверный стейт) + Zustand (клиентский). Здесь разбираем, когда имеет смысл поехать на Effector: команда растёт до 10+ человек, а фичи — стикеры, короткие видео, реакции, typing/presence — добавляют сложные асинхронные потоки.
Перед началом: Модули 1–8. Инструменты: Effector. Время: ~5 ч.
🔥 Инцидент. Команда выросла до 10 человек. Zustand-сторы — глобальные, любой может мутировать откуда угодно. Эффекты (запросы к API) живут в компонентах — тестировать невозможно. При SSR — гонки (hydrate mismatch). Effector даёт явные зависимости, изоляцию сторов и SSR-безопасность.
📖 Теория
Постановка вопроса
На MVP с ≤20 пользователями Zustand + Query — минимальный по TCO выбор. Но представим рост:
- Команда 10+ фронтенд-разработчиков, параллельно пилят разные фичи.
- Новые фичи: каталог стикеров, короткие видео (чанковый аплоуд + транскодинг), реакции, «печатает…», presence, статусы доставки/прочтения.
- Требования: предсказуемость в чужом коде на ревью, единый паттерн оркестрации, лёгкое тестирование, трейсинг «что вызвало перерисовку/событие».
Вопрос: становится ли в этих условиях Effactor более правильным выбором, чем Zustand?
Почему Effector «раскрывается» на масштабе
| Качество | На Zustand | На Effector | Почему это важно для 10+ команды |
|---|---|---|---|
| Поток данных | неявный (set(...)) | явный Event → Store → Effect | на ревью видно, кто и зачем меняет стейт |
| Изоляция владения | «плоские» сторы на фичу | createDomain по фиче/команде | разные люди не конфликтуют, чёткие границы (ложится на FSD) |
| Асинхрон-оркестрация | ad-hoc async в экшенах | sample, attach, guard, restore | единый паттерн для сложных потоков (видео, аплоуд, очереди) |
| Тестируемость | мокай стор | диспетч events, assert stores (чистые функции) | простые юнит-тесты, стабильный CI |
| Трейсинг/отладка | DevTools сторов | граф событий + DevTools | находишь «откуда прилетело» в большой кодовой базе |
| «Скрытые» перерендеры | редко | нет (явные подписки) | предсказуемый перформанс на тяжёлых экранах |
Итог: сильные стороны Zustand (простота, минимум кода) на масштабе превращаются в слабость — без единого паттерна каждая фича пишется «как получилось», а дебаг чужого потока становится дороже.
Как будущие фичи ложатся на Effector
Стикеры (каталог, поиск, recently-used)
Серверные данные (каталог, паки) — по-прежнему TanStack Query (кэш, инвалидация).
Клиентское (выбор пака, недавно использованные, ввод) — Effector-стор + effects для подгрузки.
sample связывает ввод → эффект → стор без расползания логики по компонентам.
Короткие видео (самая «тяжёлая» фича)
Аплоед — это state machine: выбор файла → чанковый аплоуд с прогрессом → поллинг статуса транскодинга → готово/ошибка → ретрай. На Effector это описывается декларативно:
tsimport { createEvent, createStore, createEffect, sample, guard } from "effector" export const videoSelected = createEvent<File>() export const retry = createEvent() const uploadChunkFx = createEffect(async (chunk: Blob) => { /* POST chunk, вернуть прогресс */ }) export const $upload = createStore<{ progress: number; status: "idle" | "uploading" | "processing" | "done" | "error" }>({ progress: 0, status: "idle", }) .on(uploadChunkFx.done, (s) => ({ ...s, progress: Math.min(100, s.progress + 10) })) .on(uploadChunkFx.fail, () => ({ progress: 0, status: "error" })) // старт аплоуда при выборе файла sample({ clock: videoSelected, target: uploadChunkFx }) // ретрай только из ошибки guard({ clock: retry, source: $upload, filter: (s) => s.status === "error", target: uploadChunkFx })
На Zustand это та же логика, но размазанная по async-функциям и флагам — на масштабе её
сложнее читать и тестировать. Effector даёт единый, трассируемый граф.
Реакции / typing / presence / read-receipts
Всё это — WebSocket-события → агрегация в сторе (debounce, дедуп, буфер).
Effector хорошо ложится: WS-клиент кидает события, sample/guard фильтруют и обновляют сторы.
Дедупликация по message_id — отдельный guard в одну строку.
Цена для команды 10+
| Статья затрат | Оценка |
|---|---|
| Онбординг одного разработчика на Effector | 8–16 ч (модель event/store/effect, sample/attach, домены) |
| Онбординг 10 человек | ~80–160 ч совокупно (можно сократить шаблонами и парными сессиями) |
| Найм | сложнее, чем Zustand/Redux (пул меньше глобально, больше в RU/CIS) |
| Бойлерплейт на тривиальную фичу | выше (Event + Store + Effect + sample); нужны шаблоны/кодогенерация, чтобы не терять скорость |
| Конвенции/линтинг | обязательно (effector/reducer, effector/no-ambiguity и т.п.) — иначе граф событий превратится в спагетти |
| Миграция с Zustand | средняя: клиентский стейт переносится по фичам, Query остаётся |
Правило решения
Выбирай Effector, если:
- команда ≥8–10 фронтендеров и/или быстрый рост;
- в продукте сложные асинхронные потоки (медиа-аплоуд, очереди, многошаговые сценарии, тяжёлый real-time);
- важна аудитируемость потока данных (финдфинг, compliance, частые ревью);
- готов инвестировать в конвенции и онбординг.
Оставайся на Zustand + Query, если:
- команда маленькая (1–4), MVP, ≤сотен пользователей;
- фичи в основном CRUD + простой real-time;
- приоритет — скорость и минимум абстракций.
Миграция Zustand → Effector (когда решили расти)
- Query остаётся — серверный кэш не трогаем.
- Переносим по одной фиче: стартуем с самой «потоковой» (например, отправка сообщения или виде-аплоуд) — на ней команда учится модели.
- Вводим
domain-изоляцию по FSD-слайсам сразу — это половина пользы Effector на масштабе. - Заводим ESLint-правила
eslint-plugin-effectorи шаблон фичи (Event/Store/Effect/sample) — чтобы не плодить вариации. - Тесты: диспетч events → assert
$store.getState()— это самое большое преимущество для CI.
Вердикт
- Для курса и MVP — остаёмся на Zustand + TanStack Query (минимальный TCO, лучшая обучаемость).
- Effector — документируем как «путь масштабирования»: понятный, предсказуемый, хорошо тестируемый выбор, когда команда вырастает до 10+ и появляются медиа-фичи и сложные потоки.
- Стикеры/видео/реакции — это ровно те сценарии, ради которых Effector и стоит рассматривать:
на них его граф событий и
sample-оркестрация окупают более крутой порог входа.
Практика модуля: реализовать аплоуд короткого видео двумя способами (Zustand и Effector), сравнить читаемость, тестируемость и трейсинг. Это даст студентам честную картину компромисса.
💱 Компромиссы (Trade-offs)
| Решение | Плюсы | Минусы |
|---|---|---|
| Zustand + Query | Минимум кода, быстрый онбординг, низкий TCO | Глобальные сторы, неявный поток, сложно на масштабе |
| Effector (Event→Store→Effect) | Явный граф, изоляция доменов, лёгкие тесты | Бойлерплейт, выше порог входа |
createDomain по фичам | Чёткие границы владения, ложится на FSD | Нужно следить за структурой доменов |
fork() для SSR/тестов | Изоляция состояния без утечек между запросами | Дисциплина: свой scope на запрос/тест |
| Миграция по одной фичи | Постепенный переезд, Query не трогаем | Временно две парадигмы в одной кодовой базе |
🧩 Проверь себя
🔗 Мостик к Модулю 10
Состояние масштабируемо. Измерим производительность — Core Web Vitals.