import { type Event, type EventCallable } from "effector";
Event в effector представляет действие пользователя, шаг в процессе приложения, команду к выполнению или намерение внести изменения и многое другое. Этот юнит предназначен для переноса информации/намерений/состояния в приложении, а не для хранения состояния.
EventCallable<T>
Создание
Существует множество способов создания события:
- Самый распространенный
createEvent
- С помощью Domain
createEvent
- Через методы Event и его суперкласс методы EventCallable
- Некоторые методы Effect возвращают новые события и события только для чтения
- Операторы, такие как:
createApi
Объявление типов
Событие переносит данные, и в экосистеме TypeScript каждый набор данных должен иметь определенный тип. Когда событие создается явно с помощью createEvent
, тип аргумента должен быть указан в качестве дженерика:
import { createEvent } from "effector";
interface ItemAdded {
id: string;
title: string;
}
const itemAdded = createEvent<ItemAdded>();
В большинстве случаев нет необходимости использовать void
вместе с другим типом (). Используйте Event<void | number>
void
только для объявления Event или EventCallable вообще без аргумента. Поэтому можно отправить данные из события с аргументом в событие без аргумента.
sample({
clock: withData, // Event<number>
target: withoutData, // Event<void>
});
Мы настоятельно рекомендуем использовать null
для пустых значений, если это подразумевается:
import { createEvent } from "effector";
const maybeDataReceived = createEvent<Data | null>();
// maybeDataReceived: EventCallable<Data | null>
Подробнее в разделе объяснений.
Вызов как функция event(argument)
Инициирует событие с переданным аргументом, который, в свою очередь, активирует всех зарегистрированных подписчиков.
Подробнее в разделе объяснений.
Formulae
const event: EventCallable<T>;
event(argument: T): T;
event
, вызываемый как функция, всегда возвращает свойargument
как есть- все подписчики события получают переданный аргумент
- когда
T
— этоvoid
,event
можно вызывать без аргументов T
по умолчанию — этоvoid
, поэтому дженерик можно опустить
В Effector любое событие поддерживает только один аргумент.
Вызов события с двумя или более аргументами, как в случае someEvent(first, second)
, невозможен.
Все аргументы, кроме первого, будут проигнорированы. Основная команда внедрила это правило по специфическим причинам, связанным с проектированием и функциональностью.
Аргументы
argument
— значение типаT
. Он необязателен, если событие определено какEventCallable<void>
.
Ошибки
eventCallable-call-argument-throws
Когда пользователь попытался вызвать Event
. В большинстве случаев это происходит, когда вы пытаетесь вызвать производное событие:
const numberReceived = createEvent<number>(); // EventCallable<number>
const stringifiedReceived = numberReceived.map((number) => String(number)); // Event<string>
stringifiedReceived("123"); // ВЫЗЫВАЕТ ОШИБКУ!
То же самое относится ко всем методам, возвращающим Event
.
Корректное использование: создайте отдельное событие через createEvent
и свяжите их с помощью sample
:
const numberReceived = createEvent<number>();
const stringifiedReceived = createEvent<string>();
sample({
clock: numberReceived,
fn: (number) => String(number),
target: stringifiedReceived,
});
stringifiedReceived("123"); // ОК
вызов юнита из чистой функции не поддерживается, используйте вместо этого операторы, такие как sample
Происходит, когда события или эффекты вызываются из чистых функций, таких как мапперы:
const someHappened = createEvent<number>();
const another = createEvent();
const derived = someHappened.map((number) => {
another(); // ВЫЗЫВАЕТ ОШИБКУ!
return String(number);
});
Корректное использование: используйте sample
:
const someHappened = createEvent<number>();
const another = createEvent();
const derived = createEvent<string>();
sample({
clock: someHappened,
target: another,
});
// То же самое, что и .map(), но с использованием `target`
sample({
clock: someHappened,
fn: (number) => String(number),
target: derived,
});
Возвращает
T
: Представляет то же значение, которое передается в event
.
Типы
import { createEvent, Event } from "effector";
const someHappened = createEvent<number>();
// someHappened: EventCallable<number>
someHappened(1);
const anotherHappened = createEvent();
// anotherHappened: EventCallable<void>
anotherHappened();
Событие может быть создано с одним дженериком. По умолчанию этот аргумент установлен в void
, что означает, что событие не принимает параметры.
Методы
Поскольку фабрика createEvent
создает EventCallable
для вас, сначала будут описаны его методы, даже несмотря на то, что это расширение типа Event
.
Все методы и свойства из Event также доступны на экземпляре EventCallable
.
Вы можете считать EventCallable и Event типом и его суперклассом:
EventCallable<T> extends Event<T>
.prepend(fn)
Создает новый EventCallable
, который вызывается, при его срабатывании передает преобразованные данные в исходное событие.
Работает как обратный .map
. В случае .prepend
данные преобразуются до срабатывания исходного события, а в случае .map
данные преобразуются после срабатывания исходного события.
Если исходное событие принадлежит какому-либо домену, то новое событие также будет ему принадлежать.
Formulae
const first: EventCallable<T>;
const second: EventCallable<T> = first.prepend(fn);
- Когда второе событие срабатывает
- Вызвать
fn
с аргументом второго события - Вызвать первое событие с результатом
fn()
Аргументы
fn
(Function): Функция, которая получаетargument
, и должна быть чистой.
Ошибки
unit call from pure function is not supported, use operators like sample instead
Происходит, когда события или эффекты вызываются из чистых функций, таких как мапперы:
const someHappened = createEvent<string>();
const another = createEvent<number>();
const reversed = someHappened.prepend((input: number) => {
another(input); // ВЫЗЫВАЕТ ОШИБКУ!
return String(input);
});
Корректное использование: используйте sample
:
const someHappened = createEvent<string>();
const another = createEvent<number>();
const reversed = createEvent<number>();
// То же самое, что и .prepend(), но с использованием `sample`
sample({
clock: reversed,
fn: (input) => String(input),
target: someHappened,
});
sample({
clock: reversed,
target: another,
});
Возвращает
EventCallable<T>
: Новое событие.
Типы
TypeScript требует явного указания типа аргумента функции fn
:
import { createEvent } from "effector";
const original = createEvent<{ input: string }>();
const prepended = original.prepend((input: string) => ({ input }));
// ^^^^^^ здесь
Тип аргумента исходного события и результирующий тип функции fn
должны совпадать.
Примеры
Базовый пример
import { createEvent } from "effector";
const userPropertyChanged = createEvent();
userPropertyChanged.watch(({ field, value }) => {
console.log(`Свойство пользователя "${field}" изменилось на ${value}`);
});
const changeName = userPropertyChanged.prepend((name) => ({
field: "name",
value: name,
}));
const changeRole = userPropertyChanged.prepend((role) => ({
field: "role",
value: role.toUpperCase(),
}));
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"] } }
Ознакомьтесь со всеми другими методами в Event.
Event<T>
Event — это суперкласс EventCallable
с иным подходом. Вызов Event не разрешен, и он не может быть использован в качестве target
в операторе sample
и т.д.
Основная цель Event — быть вызванным внутренним кодом в библиотеке effector или её экосистеме.
Например, метод .map()
возвращает Event, который затем вызывается самим методом .map()
.
Нет необходимости для кода пользователя напрямую вызывать такой Event.
Если вам необходимо вызвать Event, возможно, следует пересмотреть и реорганизовать логику вашего приложения.
Все функции, предоставляемые Event, также поддерживаются в EventCallable.
Создание
Невозможно вручную создать Event, но некоторые методы и операторы возвращают производные события, они возвращают тип
Event<T>
:
- Методы Event, такие как:
.map(fn)
,.filter({fn})
и т.д. - Свойство Store: ‘.updates’
- Методы и свойства Effect
- операторы, такие как:
sample
,merge
Ошибки
- Ошибки, связанные с некорректным использованием: Подробнее в разделах, посвящённых конкретным методам.
Объявление типов
Это становится необходимым в тех случаях, когда фабрика или библиотека требует события для подписки на его обновления, чтобы обеспечить надлежащую интеграцию и взаимодействие с предоставленным функционалом:
const event: Event<T>;
Методы
.map(fn)
Создает новое производное событие, которое будет вызвано после того, как будет вызвано исходное событие, используя результат функции fn
в качестве его аргумента. Эта специальная функция позволяет вам разбивать и управлять потоком данных, а также извлекать или преобразовывать данные в рамках вашей модели бизнес-логики.
Formulae
const first: Event<T> | EventCallable<T>;
const second: Event<F> = first.map(fn);
- Когда
first
срабатывает, передайте данные изfirst
вfn
. - Вызовите
second
с результатом вызоваfn()
в качестве полезной нагрузки. - Функция
fn
вызывается каждый раз, когда срабатываетfirst
. - Также
second
срабатывает каждый раз, когда срабатываетfirst
.
Аргументы
fn
(Function): Функция, получающаяargument
, и должна быть чистой.
Ошибки
unit call from pure function is not supported, use operators like sample instead
Происходит, когда события или эффекты вызываются из чистых функций, таких как мапперы:
const someHappened = createEvent<number>();
const another = createEvent();
const derived = someHappened.map((number) => {
another(); // ВЫЗЫВАЕТ ОШИБКУ!
return String(number);
});
Корректное использование: используйте sample
:
const someHappened = createEvent<number>();
const another = createEvent();
const derived = createEvent<string>();
sample({
clock: someHappened,
target: another,
});
// То же самое, что и .map(), но с использованием `target`
sample({
clock: someHappened,
fn: (number) => String(number),
target: derived,
});
Возвращает
Event<T>
: Новое событие.
Типы
Результирующий тип функции fn
будет использован для определения типа производного события.
import { createEvent } from "effector";
const first = createEvent<number>();
// first: Event<number>
const second = first.map((count) => count.toString());
// second: Event<string>
Событие first
может быть представлено как Event<T>
, так и EventCallable<T>
.
Событие second
всегда будет представлено как Event<T>
.
Примеры
import { createEvent } from "effector";
const userUpdated = createEvent();
// вы можете разбить поток данных с помощью метода .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]
.filter({ fn })
Этот метод генерирует новое производное событие, которое будет вызвано после исходного события, но только если функция fn
вернет true
. Эта специальная функция позволяет вам разбить поток данных на ветви и подписаться на них в рамках модели бизнес-логики.
sample с аргументом filter
является предпочтительным методом фильтрации.
Formulae
const first: Event<T> | EventCallable<T>;
const second: Event<T> = first.filter({ fn });
- Когда
first
срабатывает, передайте данные изfirst
вfn
. - Событие
second
будет срабатывать только еслиfn
вернетtrue
, с аргументом из событияfirst
. - Функция
fn
вызывается каждый раз, когда срабатываетfirst
. - Также событие
second
срабатывает каждый раз, когда срабатываетfirst
и fn возвращаетtrue
.
Аргументы
fn
(Function): Функция, получающаяargument
, и должна быть чистой.
По историческим причинам fn
должен использовать объектную форму, потому что event.filter(fn)
был псевдонимом для Event filterMap.
Используйте его всегда так .filter({ fn })
.
Ошибки
unit call from pure function is not supported, use operators like sample instead
Происходит, когда события или эффекты вызываются из чистых функций, таких как guards:
const countReceived = createEvent<number>();
const eachReceived = createEvent<number>();
const receivedEven = someHappened.filter({
fn(count) {
eachReceived(count); // ВЫЗЫВАЕТ ОШИБКУ!
return count % 2 === 0;
},
});
Корректное использование: используйте sample
для вызова eachReceived
:
const countReceived = createEvent<number>();
const eachReceived = createEvent<number>();
const receivedEven = someHappened.filter({
fn(count) {
return count % 2 === 0;
},
});
sample({
clock: someHappened,
target: eachReceived,
});
Возвращает
Event<T>
: Новое событие.
Типы
Метод .filter()
всегда возвращает Event. Также это событие будет иметь тот же тип, что и исходный:
import { createEvent } from "effector";
const numberReceived = createEvent<number>();
// numberReceived: Event<number>
const evenReceived = numberReceived.filter({
fn: (number) => number % 2 === 0,
});
// evenReceived: Event<number>
evenReceived.watch(console.info);
numberReceived(5); // ничего
numberReceived(2); // => 2
Примеры
import { createEvent, createStore } from "effector";
const numbers = createEvent();
const positiveNumbers = numbers.filter({
fn: ({ x }) => x > 0,
});
const $lastPositive = createStore(0).on(positiveNumbers, (n, { x }) => x);
$lastPositive.watch((x) => {
console.log("последнее положительное:", x);
});
// => последнее положительное: 0
numbers({ x: 0 });
// нет реакции
numbers({ x: -10 });
// нет реакции
numbers({ x: 10 });
// => последнее положительное: 10
Содержательный пример
Предположим стандартную ситуацию, когда вы хотите купить кроссовки в магазине, но нужного размера нет. Вы подписываетесь на конкретный размер модели кроссовок и, кроме того, хотите получать уведомления, если они появятся, и игнорировать любые другие уведомления. В таком случае фильтрация будет полезной. Фильтрация событий работает аналогично. Если filter
возвращает true
, событие будет вызвано.
const sneackersReceived = createEvent<Sneakers>();
const uniqueSizeReceived = sneackersReceived.filter({
fn: (sneackers) => sneackers.size === 48,
});
.filterMap(fn)
Этот метод генерирует новое производное событие, которое может быть вызвано после исходного события, но с преобразованным аргументом. Этот специальный метод позволяет одновременно преобразовывать данные и фильтровать срабатывание события.
Этот метод похож на объединение .filter()
и .map()
. Это и есть причина его создания: невозможность фильтрации событий.
Этот метод наиболее полезен с API JavaScript, которые иногда возвращают undefined
.
Formulae
const first: Event<T> | EventCallable<T>;
const second: Event<F> = first.filterMap(fn);
- Когда
first
срабатывает, вызовитеfn
с полезной нагрузкой изfirst
. - Если
fn()
вернулundefined
, не вызывайтеsecond
. - Если
fn()
вернул какие-либо данные, вызовитеsecond
с данными изfn()
.
Аргументы
fn
(Function): Функция, получающаяargument
, должна быть чистой.
Функция fn
должна возвращать некоторые данные. Если возвращается undefined
, обновление производного события будет пропущено.
Ошибки
unit call from pure function is not supported, use operators like sample instead
Происходит, когда события или эффекты вызываются из чистых функций, таких как мапперы:
const countReceived = createEvent<number>();
const eachReceived = createEvent<number>();
const receivedEven = someHappened.filterMap((count) => {
eachReceived(count); // ВЫЗЫВАЕТ ОШИБКУ!
return count % 2 === 0 ? Math.abs(count) : undefined;
});
Корректное использование: используйте sample
для вызова eachReceived
:
const countReceived = createEvent<number>();
const eachReceived = createEvent<number>();
const receivedEven = someHappened.filterMap((count) => {
return count % 2 === 0 ? Math.abs(count) : undefined;
});
sample({
clock: someHappened,
target: eachReceived,
});
Возвращает
Event<T>
: Новое событие.
Типы
Тип для производного события автоматически выводится из объявления функции fn
.
Нет необходимости явно задавать тип для переменной или дженерика:
import { createEvent } from "effector";
const first = createEvent<number>();
// first: Event<number>
const second = first.filterMap((count) => {
if (count === 0) return;
return count.toString();
});
// second: Event<string>
Событие first
может быть представлено как Event<T>
, так и EventCallable<T>
.
Событие second
всегда будет представлено как Event<T>
.
Примеры
import { createEvent } from "effector";
const listReceived = createEvent<string[]>();
// Array.prototype.find() возвращает `undefined`, когда элемент не найден
const effectorFound = listReceived.filterMap((list) => list.find((name) => name === "effector"));
effectorFound.watch((name) => console.info("найден", name));
listReceived(["redux", "effector", "mobx"]); // => найден effector
listReceived(["redux", "mobx"]);
Задача в терминах эффектора
Рассмотрим сценарий, когда вы заходите в продуктовый магазин с конкретной задачей: вам нужно купить 10 яблок, но только если они красные. Если они не красные, вам не повезло. Рассмотрим шаги:
- Возьмите одно яблоко;
- Посмотрите, красное ли оно (положите в пакет) или нет (возьмите другое).
И вы повторяете это, пока не выполните задачу
. Теперь подумайте об этом в терминах effector, и мы рассмотрим положительный случай:
- Взять яблоко — событие;
- Посмотреть, красное ли оно — фильтр;
- Вы сохраняете его — маппинг;
- Положить в пакет — событие.
- Пакет — стор.
.watch(watcher)
Этот метод позволяет вам вызывать обратный вызов при каждом срабатывании события с аргументом события.
Метод watch
не обрабатывает и не сообщает о исключениях, не управляет завершением асинхронных операций и не решает проблемы сгона данных.
Его основное предназначение — для краткосрочного отладки и логирования.
Подробнее в разделе объяснений.
Formulae
const event: Event<T> | EventCallable<T>;
const unwatch: () => void = event.watch(fn);
- Функция
fn
будет вызвана при каждом срабатыванииevent
, передав аргумент события вfn
. - Когда
unwatch
вызывается, перестаньте вызыватьfn
при каждом срабатыванииevent
.
Аргументы
watcher
(Watcher): Функция, получающаяargument
из события.
Возвращает
Subscription: Функция для отмены подписки.
Примеры
import { createEvent } from "effector";
const sayHi = createEvent();
const unwatch = sayHi.watch((name) => console.log(`${name}, привет!`));
sayHi("Питер"); // => Питер, привет!
unwatch();
sayHi("Дрю"); // => ничего не произошло
.subscribe(observer)
Это низкоуровневый метод для интеграции события со стандартным шаблоном Observable
.
Вам не нужно использовать этот метод самостоятельно. Он используется под капотом движками рендеринга и так далее.
Подробнее:
Свойства
Этот набор свойств в основном задается effector/babel-plugin
или @effector/swc-plugin
. Таким образом, они существуют только при использовании Babel или SWC.
.sid
Это уникальный идентификатор для каждого события.
Важно отметить, что SID не изменяется при каждом запуске приложения, он статически записывается в пакет вашего приложения для абсолютной идентификации юнитов.
Это может быть полезно для отправки событий между рабочими или сервером/браузером: examples/worker-rpc.
Имеет тип string | null
.
.shortName
Это свойство типа string
, содержащее имя переменной, в которой объявлено событие.
import { createEvent } from "effector";
const demo = createEvent();
// demo.shortName === 'demo'
Но переопределение события в другую переменную ничего не изменит:
const another = demo;
// another.shortName === 'demo'
.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"] }
Типы
import { type EventPayload } from "effector";
EventPayload<E>
Извлекает тип полезной нагрузки из Event
или EventCallable
.
const event: Event<Payload>;
type Payload = EventPayload<typeof event>;
Документация на английском языке - самая актуальная, поскольку её пишет и обновляет команда effector. Перевод документации на другие языки осуществляется сообществом по мере наличия сил и желания.
Помните, что переведенные статьи могут быть неактуальными, поэтому для получения наиболее точной и актуальной информации рекомендуем использовать оригинальную англоязычную версию документации.