Event API

import { type Event, type EventCallable, createEvent } from "effector";

const event = createEvent();

Событие в effector представляет действие пользователя, шаг в процессе приложения, команду к выполнению или намерение внести изменения и многое другое.
Событие служит как точка входа в реактивный поток данных — простой способ сказать приложению “что-то произошло”.

это ваше каноничное событие

Если вы не знакомы с событиями и способами работы с ними, то вам сюда Что такое события и как работать с ними.

Типы событий

Важно понять, что существуют два типа событий:

  1. Обычное событие, которое создается с помощью createEvent, .prepend; эти события имеют тип EventCallable и могут быть вызваны, либо использованы в target метода sample.
  2. Производное событие, который создается с помощью .map, .filter, .filterMap. Такие события имеют тип Event и их нельзя вызывать или передавать в target, effector сам вызовет их в нужном порядке, однако вы можете подписываться на эти события с помощью sample или watch.

Интерфейс Event

Доступные методы и свойства событий:

Метод/Свойство
Описание
prepend(fn)Создаёт новое событие EventCallable, трансформируют входные данные через fn и передает в исходное событие.
map(fn)Создаёт новое событие типа Event с результатом вызова fn после срабатывания исходного события.
filter({fn}) Создаёт новое событие типа Event, срабатывающий только если fn возвращает true.
filterMap(fn)Создаёт событие типа Event, срабатывающий с результатом fn, если тот не вернул undefined.
watch(watcher)Добавляет слушатель, вызывающий watcher при каждом срабатывании события.
subscribe(observer)Низкоуровневый метод для интеграции события со стандартным шаблоном Observable.
sidУникальный идентификатор юнита (unit).
shortNameСвойство типа string, содержащее имя переменной, в которой объявлено событие.
compositeNameКомплексное имя Event (включая домен и короткое имя) — удобно для логирования и трассировки.

Методы событий

.prepend(fn)

информация

Этот метод существует только для обычных событий (EventCallable)! Это значит что этот метод может использоваться только на событиях созданных с помощью createEvent.

Создает новое событие EventCallable, который можно вызвать. При его срабатывании вызвает fn и передает преобразованные данные в исходное событие.

  • Формула
const second = first.prepend(fn);
  • Тип
event.prepend<Before = void>(
  fn: (_: Before) => Payload
): EventCallable<Before>
  • Примеры
import { createEvent } from "effector";

// исходное событие
const userPropertyChanged = createEvent();

const changeName = userPropertyChanged.prepend((name) => ({
  field: "name",
  value: name,
}));
const changeRole = userPropertyChanged.prepend((role) => ({
  field: "role",
  value: role.toUpperCase(),
}));

userPropertyChanged.watch(({ field, value }) => {
  console.log(`Свойство пользователя "${field}" изменилось на ${value}`);
});

changeName("john");
// => Свойство пользователя "name" изменилось на john

changeRole("admin");
// => Свойство пользователя "role" изменилось на ADMIN

changeName("alice");
// => Свойство пользователя "name" изменилось на alice

Открыть пример

Вы можете считать этот метод функцией-обёрткой. Допустим, у нас есть функция с неидеальным API, но нам нужно часто её вызывать:

import { sendAnalytics } from "./analytics";

export function reportClick(item: string) {
  const argument = { type: "click", container: { items: [arg] } };
  return sendAnalytics(argument);
}

Это именно то, как работает .prepend():

import { sendAnalytics } from "./analytics";

export const reportClick = sendAnalytics.prepend((item: string) => {
  return { type: "click", container: { items: [arg] } };
});

reportClick("example");
// reportClick сработал "example"
// sendAnalytics сработал с { type: "click", container: { items: ["example"] } }
  • Детальное описание

Работает как обратный .map. В случае .prepend данные преобразуются до срабатывания исходного события, а в случае .map данные преобразуются после срабатывания.

Если исходное событие принадлежит какому-либо домену, то новое событие также будет ему принадлежать.

  • Возвращаемое значение

Возвращает новое событие EventCallable.

Ознакомьтесь со всеми другими методами в Event.


.map(fn)

Создает новое производное событие, которое будет вызвано после того, как будет вызвано исходное событие, используя результат функции fn в качестве его аргумента.

Чистота наше все!

Функция fn должна быть чистой.

  • Формула
// Событие любого типа, как производное так и обычное
const first: Event<T> | EventCallable<T>;
const second: Event<F> = first.map(fn);
  • Тип
event.map<T>(fn: (payload: Payload) => T): Event<T>
  • Примеры
import { createEvent } from "effector";

const userUpdated = createEvent<{ name: string; role: string }>();

// вы можете разбить поток данных с помощью метода .map()
const userNameUpdated = userUpdated.map(({ user }) => name);

// либо преобразовать данные
const userRoleUpdated = userUpdated.map((user) => user.role.toUpperCase());

userNameUpdated.watch((name) => console.log(`Имя пользователя теперь [${name}]`));
userRoleUpdated.watch((role) => console.log(`Роль пользователя теперь [${role}]`));

userUpdated({ name: "john", role: "admin" });
// => Имя пользователя теперь [john]
// => Роль пользователя теперь [ADMIN]

Открыть пример

  • Детальное описание

Метод .map позволяет вам разбивать и управлять потоком данных, а также извлекать или преобразовывать данные в рамках вашей модели бизнес-логики.

  • Возвращаемое значение

Возвращает новое производное событие.


.filter({ fn })

совет

sample с аргументом filter является предпочтительным методом фильтрации:

const event = createEvent();

const filteredEvent = sample({
  clock: event,
  filter: () => true,
});

Метод .filter генерирует новое производное событие, которое будет вызвано после исходного события,в случае если функция fn вернет true. Эта специальная функция позволяет вам разбить поток данных на ветви и подписаться на них в рамках модели бизнес-логики.
Это очень удобно, если мы хотим на события которые срабатывают по условию.

  • Формула
// Событие любого типа, как производное так и обычное
const first: Event<T> | EventCallable<T>;
const second: Event<T> = first.filter({ fn });
  • Тип
event.filter(config: {
  fn(payload: Payload): boolean
}): Event<Payload>
  • Примеры
import { createEvent, createStore } from "effector";

const numbers = createEvent();
const positiveNumbers = numbers.filter({
  fn: ({ x }) => x > 0,
});

const $lastPositive = createStore(0);

$lastPositive.on(positiveNumbers, (n, { x }) => x);

$lastPositive.watch((x) => {
  console.log("последнее положительное:", x);
});

// => последнее положительное: 0

numbers({ x: 0 });
// нет реакции

numbers({ x: -10 });
// нет реакции

numbers({ x: 10 });
// => последнее положительное: 10

Открыть пример

  • Возвращаемое значение

Возвращает новое производное событие.


.filterMap(fn)

наш любимый sample

Этот метод также можно заменить на операцию sample с аргументами filter + fn:

const event = createEvent();

const filteredAndMappedEvent = sample({
  clock: event,
  filter: () => true,
  fn: () => "value",
});

Этот метод генерирует новое производное событие, которое может быть вызвано после исходного события, но с преобразованным аргументом. Этот специальный метод позволяет одновременно преобразовывать данные и фильтровать срабатывание события.

Этот метод наиболее полезен с API JavaScript, которые иногда возвращают undefined.

  • Формула
// Событие любого типа, как производное так и обычное
const first: Event<T> | EventCallable<T>;
const second: Event<F> = first.filterMap(fn);
  • Тип
event.filterMap<T>(fn: (payload: Payload) => T | undefined): Event<T>
  • Примеры
import { createEvent } from "effector";

const listReceived = createEvent<string[]>();

// Array.prototype.find() возвращает `undefined`, когда элемент не найден
const effectorFound = listReceived.filterMap((list) => {
  return list.find((name) => name === "effector");
});

effectorFound.watch((name) => console.info("найден", name));

listReceived(["redux", "effector", "mobx"]); // => найден effector
listReceived(["redux", "mobx"]);
Внимание

Функция fn должна возвращать некоторые данные. Если возвращается undefined, вызов производного события будет пропущено.

Открыть пример

  • Возвращаемое значение

Возвращает новое производное событие.


.watch(watcher)

Метод .watch вызывается колбэк watcher каждый раз при срабатывании события.

Помните

Метод watch не обрабатывает и не сообщает о исключениях, не управляет завершением асинхронных операций и не решает проблемы гонки данных.

Его основное предназначение — для краткосрочного отладки и логирования.

Подробнее в разделе изучения.

  • Формула
// Событие любого типа, как производное так и обычное
const event: Event<T> | EventCallable<T>;
const unwatch: () => void = event.watch(fn);
  • Тип
  event.watch(watcher: (payload: Payload) => any): Subscription
  • Примеры
import { createEvent } from "effector";

const sayHi = createEvent();
const unwatch = sayHi.watch((name) => console.log(`${name}, привет!`));

sayHi("Питер"); // => Питер, привет!
unwatch();

sayHi("Дрю"); // => ничего не произошло

Открыть пример

  • Возвращаемое значение

Возвращает функцию для отмены подписки.


.subscribe(observer)

Это низкоуровневый метод для интеграции события со стандартным шаблоном Observable.

Подробнее:

Помните

Вам не нужно использовать этот метод самостоятельно. Он используется под капотом движками рендеринга и так далее.

  • Формула
const event = createEvent();

event.subscribe(observer);
  • Тип
event.subscribe(observer: Observer<Payload>): Subscription
  • Примеры
import { createEvent } from "effector";

const userLoggedIn = createEvent<string>();

const subscription = userLoggedIn.subscribe({
  next: (login) => {
    console.log("User login:", login);
  },
});

userLoggedIn("alice"); // => User login: alice

subscription.unsubscribe();
userLoggedIn("bob"); // ничего не произойдет

Свойства

Этот набор свойств в основном задается с помощью effector/babel-plugin или @effector/swc-plugin. Таким образом, они существуют только при использовании Babel или SWC.

.sid

Это уникальный идентификатор для каждого события.

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

Это может быть полезно для отправки событий между рабочими или сервером/браузером: examples/worker-rpc.

  • Тип
interface Event {
  sid: string | null;
}

.shortName

Это свойство содержащее имя переменной, в которой объявлено событие.

import { createEvent } from "effector";

const demo = createEvent();
// demo.shortName === 'demo'

Но переопределение события в другую переменную ничего не изменит:

const another = demo;
// another.shortName === 'demo'
  • Тип
interface Event {
  shortName: string;
}

.compositeName

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

Помните

Обычно, если требуется длинное имя, лучше передать его явно в поле name.

import { createEvent, createDomain } from "effector";

const first = createEvent();
const domain = createDomain();
const second = domain.createEvent();

console.log(first.compositeName);
// {
//     "shortName": "first",
//     "fullName": "first",
//     "path": [
//         "first"
//      ]
// }

console.log(second.compositeName);
// {
//     "shortName": "second",
//     "fullName": "domain/second",
//     "path": [
//         "domain",
//         "second"
//      ]
// }
  • Тип
interface Event {
  compositeName: {
    shortName: string;
    fullName: string;
    path: Array<string>;
  };
}

Особенности Event

  1. В Effector любое событие поддерживает только один аргумент. Вызов события с двумя или более аргументами, как в случае someEvent(first, second), будет игнорировать все аргументы кроме первого.
  2. В методах событий нельзя вызывать другие события или эффекты - функции должны быть чистыми
Перевод поддерживается сообществом

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

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

Соавторы