Как мыслить в парадигме effector
На самом деле effector это не только про управление состоянием приложения, а также и про масштабируемое построение логики вашего приложения. Effector никак не ограничивает вас в написании кода, однако если понять следующие принципы, то будет гораздо проще писать код и мыслить, когда вы используете effector:
- События – это описание вашего приложения, основа всего.
- Бизнес-логика и UI – это разные вещи, нужно стараться разделять ответственность между данными и их отображением.
События — основа всего
Каждое взаимодействие пользователя с вашим приложением – это событие. Событие не решает, что должно произойти, оно просто фиксирует факт произошедшего, например: пользователь отправил форму - formSubmitted, пользователь кликнул по кнопке обновить - refreshButtonClicked, пользователь изменил фильтр поиска - searchFilterChanged и так далее.
При этом события не ограничиваются только действиями пользователя, они также могут описывать логику вашей модели, например: явный запуск работы вашей модели (микрофронтенд или фича) - start, произошла ошибка - errorOccurred и так далее.
Не стесняйтесь заводить столько событий, сколько требуется, чтобы полно описать действия приложения, так проще видеть и отслеживать, как работает ваше приложение.
При проектировании нового функционала проще всего начать с событий, поскольку они сразу наглядны в интерфейсе.
Разделяйте бизнес-логику и UI
Effector позволяет разделять отображение (UI) и логику вашего приложения (бизнес-логику). Вся логика работы вашего приложения, как правило, должна описываться отдельно от вашего UI, в отдельном модуле, например model.ts и отдавать наружу для UI только то, что нужно для отображения или взаимодействие с пользователем.
Например, при срабатывании события formSubmitted вы можете вызвать эффект для отправки данных на сервер, еще один эффект для отправки аналитики, а также отобразить оповещение пользователю при срабатывании события:
const formSubmitted = createEvent();
const sendFormDataFx = createEffect(() => {});const sendAnalyticsFx = createEffect(() => {});const showNotificationFx = createEffect(() => {});
sample({ clock: formSubmitted, target: [sendFormDataFx, sendAnalyticsFx, showNotificationFx],});В какой-то момент у вас может изменится логика, и вы решите отправлять аналитику только после успешной отправки формы, а оповещение показывать не только при отправке формы, но и при ошибке:
const formSubmitted = createEvent();
const sendFormDataFx = createEffect(() => {});const sendAnalyticsFx = createEffect(() => {});const showNotificationFx = createEffect(() => {});
sample({ clock: formSubmitted, target: [sendFormDataFx, showNotificationFx],});
sample({ clock: sendFormDataFx.doneData, target: sendAnalyticsFx,});
sample({ clock: sendFormDataFx.failData, target: showNotificationFx,});У нас изменилась логика приложения, но UI не изменился. Нашему UI не нужно знать какие эффекты мы отправляем и что у нас меняется, все что знает наш UI это что была нажата кнопка обновления и ему нужно вызвать событие refreshButtonClicked.
В ином случае, если мы будем смешивать логику и UI, то при изменении логики нам придется менять код и в UI.
Как это выглядит в реальном приложении?
Давайте рассмотрим для примера GitHub с его функционалом для репозиториев. Каждое действие пользователя — это событие:

- Пользователь поставил/убрал звездочку -
repoStarToggled - Пользователь изменил ветку репозитория -
repoBranchChanged - Строка поиска по репозиторию изменилась -
repoFileSearchChanged - Репозиторий был форкнут -
repoForked
Всю логику приложения строить гораздо проще вокруг событий и реакций на них. UI просто сообщает о действии, а их обработка это уже часть бизнес-логики.
Упрощенный пример логики с кнопкой звездочки:
// событие – факт действияconst repoStarToggled = createEvent();
// эффекты как дополнительная реакция на события// (предположим эффекты возвращают обновленное значение)const starRepoFx = createEffect(() => {});const unstarRepoFx = createEffect(() => {});
// состояние приложенияconst $isRepoStarred = createStore(false);const $repoStarsCount = createStore(0);
// логика переключения звездочкиsample({ clock: repoStarToggled, source: $isRepoStarred, fn: (isRepoStarred) => !isRepoStarred, target: $isRepoStarred,});
// отправка запроса на сервер при переключении звездыsample({ clock: $isRepoStarred, filter: (isRepoStarred) => isRepoStarred, target: starRepoFx,});
sample({ clock: $isRepoStarred, filter: (isRepoStarred) => !isRepoStarred, target: unstarRepoFx,});
// обновляем счетчикsample({ clock: [starRepoFx.doneData, unstarRepoFx.doneData], target: $repoStarsCount,});import { repoStarToggled, $isRepoStarred, $repoStarsCount } from "./repo.model.ts";
const RepoStarButton = () => { const [onStarToggle, isRepoStarred, repoStarsCount] = useUnit([ repoStarToggled, $isRepoStarred, $repoStarsCount, ]);
return ( <div> <button onClick={onStarToggle}>{isRepoStarred ? "unstar" : "star"}</button> <span>{repoStarsCount}</span> </div> );};Связанные API и статьи
- API
- Статьи
Документация на английском языке - самая актуальная, поскольку её пишет и обновляет команда effector. Перевод документации на другие языки осуществляется сообществом по мере наличия сил и желания.
Помните, что переведенные статьи могут быть неактуальными, поэтому для получения наиболее точной и актуальной информации рекомендуем использовать оригинальную англоязычную версию документации.