Явный запуск приложения
В effector события не могут быть вызваны неявно. Это дает вам больше контроля над жизненным циклом приложения и помогает избежать непредвиденного поведения.
Пример
Самый простой пример это вы можете создать что-то вроде события appStarted
и вызвать его сразу после инициализации приложения. Давайте пройдемся по коду построчно и объясним, что здесь происходит.
- Создаем
appStarted
событие.
Оно будет вызываться при запуске приложения.
import { createEvent, fork, allSettled } from 'effector';
const appStarted = createEvent();
const scope = fork();
await allSettled(appStarted, { scope });
- Создайте изолированный контекст приложения с помощью
fork()
. Это позволит создать скоуп, который будет использоваться по всему приложению.
import { createEvent, fork, allSettled } from 'effector';
const appStarted = createEvent();
const scope = fork();
await allSettled(appStarted, { scope });
- Вызовите стартовое событие
appStarted
в изолированном контексте с помощьюallSettled()
. Это гарантирует, что все вычисления, связанные с этим событием, будут завершены до продолжения выполнения кода.
import { createEvent, fork, allSettled } from 'effector';
const appStarted = createEvent();
const scope = fork();
await allSettled(appStarted, { scope });
Зачем это нужно ?
Основная цель такого подхода – это позволить нам контролировать жизненный цикл приложения. Это помогает избежать неожиданного поведения и сделать ваше приложение более предсказуемым. Допустим, у нас есть модуль со следующим кодом:
import { createStore, createEvent, sample, scopeBind } from 'effector';
const $counter = createStore(0);const increment = createEvent();
const startIncrementationIntervalFx = createEffect(() => { const boundIncrement = scopeBind(increment, { safe: true });
setInterval(() => { boundIncrement(); }, 1000);});
sample({ clock: increment, source: $counter, fn: (counter) => counter + 1, target: $counter,});
startIncrementationIntervalFx();
Тесты
Мы верим, что любое серьезное приложение должно быть покрыто тестами, поэтому мы должны изолировать жизненный цикл приложения внутри конкретного теста. В случае неявного старта (старта логики модели при выполнении модуля) будет невозможно протестировать поведение приложения в разных состояниях.
scopeBind
позволяет привязать событие к конкретному скоупу, больше деталей можете найти на странице Изолированные контексты, а также Потеря скоупа.
Теперь, чтобы протестировать приложение нам нужно замокать функцию setInterval
и проверить, что значение $counter
корректно через определенное время.
import { $counter } from './app';
test('$counter should be 5 after 5 seconds', async () => { // ... test});
test('$counter should be 10 after 10 seconds', async () => { // ... test});
Но $counter
будет увеличиваться сразу после загрузки модуля app.ts
и у нас просто не будет возможности протестировать поведение приложения в разных состояниях.
SSR
Еще одна причина использовать явный старт приложения – это серверный рендеринг (SSR). В этом случае нам нужно запускать логику приложения при каждом запросе пользователя, и это будет невозможно сделать с неявным стартом.
import * as app from './app';
function handleRequest(req, res) { // ...}
Но опять же, счетчик начнет свое выполнения сразу же после выполнения модуля (инициализации приложения), и мы не сможем запускать логику приложения при каждом запросе пользователя.
Добавим явный старт
Теперь давайе перепишем код и добавим явный старт приложения:
import { createStore, createEvent, sample, scopeBind } from 'effector';
const $counter = createStore(0);const increment = createEvent();
const startIncrementationIntervalFx = createEffect(() => { const boundIncrement = scopeBind(increment, { safe: true });
setInterval(() => { boundIncrement(); }, 1000);});
sample({ clock: increment, source: $counter, fn: (counter) => counter + 1, target: $counter,});
startIncrementationIntervalFx();
const appStarted = createEvent();sample({ clock: appStarted, target: startIncrementationIntervalFx,});
Вот и все, теперь мы можем тестировать поведение приложения в разных состояниях и запускать логику приложения при каждом запросе пользователя.
В реальных приложениях лучше добавлять не только явный старт приложения, но и явную остановку приложения. Это поможет избежать утечек памяти и непредвиденного поведения. Также вы можете реализовывать такое поведение и для фич вашего приложения, чтобы контролировать жизненный цикл каждой фичи отдельно.
В примерах выше мы использовали одно событие appStarted
для запуска всей логики приложения. В реальных приложениях лучше использовать более гранулярные события для запуска конкретной части приложения.
Связанные API и статьи
-
API
Scope
- Описание скоупа и его методовscopeBind
- Метод для привязки юнита к скоупуfork
- Оператор для создания скоупаallSettled
- Метод для вызова юнита в предоставленном скоупе и ожидания завершения всей цепочки эффектов
-
Статьи
Документация на английском языке - самая актуальная, поскольку её пишет и обновляет команда effector. Перевод документации на другие языки осуществляется сообществом по мере наличия сил и желания.
Помните, что переведенные статьи могут быть неактуальными, поэтому для получения наиболее точной и актуальной информации рекомендуем использовать оригинальную англоязычную версию документации.