Явный запуск приложения

В effector события не могут быть вызваны неявно. Это дает вам больше контроля над жизненным циклом приложения и помогает избежать непредвиденного поведения.

Пример

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

  1. Создаем appStarted событие.

Оно будет вызываться при запуске приложения.

import { createEvent, fork, allSettled } from 'effector';
const appStarted = createEvent();
const scope = fork();
await allSettled(appStarted, { scope });
  1. Создайте изолированный контекст приложения с помощью fork(). Это позволит создать скоуп, который будет использоваться по всему приложению.
import { createEvent, fork, allSettled } from 'effector';
const appStarted = createEvent();
const scope = fork();
await allSettled(appStarted, { scope });
  1. Вызовите стартовое событие appStarted в изолированном контексте с помощью allSettled(). Это гарантирует, что все вычисления, связанные с этим событием, будут завершены до продолжения выполнения кода.
import { createEvent, fork, allSettled } from 'effector';
const appStarted = createEvent();
const scope = fork();
await allSettled(appStarted, { scope });

Зачем это нужно ?

Основная цель такого подхода – это позволить нам контролировать жизненный цикл приложения. Это помогает избежать неожиданного поведения и сделать ваше приложение более предсказуемым. Допустим, у нас есть модуль со следующим кодом:

app.ts
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

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

Теперь, чтобы протестировать приложение нам нужно замокать функцию setInterval и проверить, что значение $counter корректно через определенное время.

app.test.ts
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). В этом случае нам нужно запускать логику приложения при каждом запросе пользователя, и это будет невозможно сделать с неявным стартом.

server.ts
import * as app from './app';
function handleRequest(req, res) {
// ...
}

Но опять же, счетчик начнет свое выполнения сразу же после выполнения модуля (инициализации приложения), и мы не сможем запускать логику приложения при каждом запросе пользователя.

Добавим явный старт

Теперь давайе перепишем код и добавим явный старт приложения:

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

Перевод поддерживается сообществом

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

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

Соавторы