import { attach } from "effector";
С версии effector 22.4.0 можно проверить, создан ли эффект через метод attach
— is.attached.
Создает новые эффекты на основе других эффектов и сторов. Позволяет маппить параметры и обрабатывать ошибки.
Основные случаи использования: декларативный способ передачи значений из сторов в эффекты и предобработка аргументов. Наиболее полезный случай — attach({ source, async effect })
.
Прикрепленные эффекты являются такими же полноценными объектами, как и обычные эффекты, созданные через createEffect. Вы должны размещать их в тех же файлах, что и обычные эффекты, а также можете использовать ту же стратегию именования.
Методы
attach({effect})
Создает эффект, который будет вызывать effect
с переданными параметрами как есть. Это позволяет создавать отдельные эффекты с общим поведением.
Формула
const attachedFx = attach({ effect: originalFx });
- Когда
attachedFx
вызывается,originalFx
также вызывается. - Когда
originalFx
завершается (успешно/с ошибкой),attachedFx
завершается с тем же состоянием.
Аргументы
effect
(Effect): Обернутый эффект.
Возвращает
Effect: Новый эффект.
Типы
const originalFx: Effect<Params, Done, Fail>;
const attachedFx: Effect<Params, Done, Fail> = attach({ effect: originalFx,});
В этом простом варианте attach
типы originalFx
и attachedFx
будут одинаковыми.
Примеры
Это позволяет создать локальную копию эффекта, чтобы реагировать только на вызовы из текущего локального кода.
import { createEffect, attach } from "effector";
const originalFx = createEffect((word: string) => { console.info("Напечатано:", word);});
const attachedFx = attach({ effect: originalFx });
originalFx.watch(() => console.log("originalFx"));originalFx.done.watch(() => console.log("originalFx.done"));
attachedFx.watch(() => console.log("attachedFx"));attachedFx.done.watch(() => console.log("attachedFx.done"));
originalFx("первый");// => originalFx// => Напечатано: первый// => originalFx.done
attachedFx("второй");// => attachedFx// => originalFx// Напечатано: второй// => originalFx.done// => attachedFx.done
attach({source, effect})
Создает эффект, который будет вызывать указанный эффект с данными из source
стора.
Формула
const attachedFx = attach({ source, effect: originalFx,});
- Когда
attachedFx
вызывается, данные изsource
читаются, иoriginalFx
вызывается с этими данными. - Когда
originalFx
завершается, то же состояние (успех/ошибка) передается вattachedFx
, и он завершается.
Аргументы
source
(Store |{[key: string]: Store}
): Стор или объект с сторами, значения которых будут переданы во второй аргументmapParams
.effect
(Effect): Исходный эффект.
Возвращает
Effect: Новый эффект.
Типы
Вам не нужно явно указывать типы для каждого объявления. Следующий пример предназначен для лучшего понимания.
В большинстве случаев вы будете писать код так, без явных типов для let
/const
:
const originalFx = createEffect<OriginalParams, SomeResult, SomeError>(async () => {});const $store = createStore(initialValue);
const attachedFx = attach({ source: $store, effect: originalFx,});
Один стор
const originalFx: Effect<T, Done, Fail>;const $store: Store<T>;
const attachedFx: Effect<void, Done, Fail> = attach({ source: $store, effect: originalFx,});
Попробуйте в песочнице TypeScript
Типы стора в source
и параметров effect
должны совпадать.
Но attachedFx
будет опускать тип параметров, что означает, что прикрепленный эффект не требует никаких параметров.
Объект стора
const originalFx: Effect<{ a: A; b: B }, Done, Fail>;const $a: Store<A>;const $b: Store<B>;
const attachedFx: Effect<void, Done, Fail> = attach({ source: { a: $a, b: $b }, effect: originalFx,});
Попробуйте в песочнице TypeScript
Типы объекта source
должны совпадать с параметрами originalFx
. Но attachedFx
будет опускать тип параметров, что означает, что прикрепленный эффект не требует никаких параметров.
Примеры
import { createEffect, createStore, attach } from "effector";
const requestPageFx = createEffect<{ page: number; size: number }, string[]>( async ({ page, size }) => { console.log("Запрошено", page); return page * size; },);
const $page = createStore(1);const $size = createStore(20);
const requestNextPageFx = attach({ source: { page: $page, size: $size }, effect: requestPageFx,});
$page.on(requestNextPageFx.done, (page) => page + 1);
requestPageFx.doneData.watch((position) => console.log("requestPageFx.doneData", position));
await requestNextPageFx();// => Запрошено 1// => requestPageFx.doneData 20
await requestNextPageFx();// => Запрошено 2// => requestPageFx.doneData 40
await requestNextPageFx();// => Запрошено 3// => requestPageFx.doneData 60
attach({source, async effect})
Создает эффект, который будет вызывать асинхронную функцию с данными из source
стора.
Формула
const attachedFx = attach({ source, async effect(source, params) {},});
- Когда
attachedFx
вызывается, данные изsource
читаются, и вызывается функцияeffect
. - Когда функция
effect
возвращает успешныйPromise
,attachedFx
завершается с данными из функции какattachedFx.done
. - Когда функция
effect
выбрасывает исключение или возвращает отклоненныйPromise
,attachedFx
завершается с данными из функции какattachedFx.fail
.
Аргументы
effect
(Function):(source: Source, params: Params) => Promise<Result> | Result
source
(Store |{[key: string]: Store}
): Стор или объект с сторами, значения которых будут переданы в первый аргументeffect
.
Возвращает
Effect: Новый эффект.
Использование с областью видимости
Любые эффекты, вызванные внутри функции async effect
, будут распространять область видимости.
const outerFx = createEffect((count: number) => { console.log("Попадание", count);});
const $store = createStore(0);const attachedFx = attach({ source: $store, async effect(count, _: void) {},});
Область видимости теряется, если есть любые асинхронные вызовы функций:
const attachedFx = attach({ source: $store, async effect(source) { // Здесь всё в порядке, эффект вызывается const resultA = await anotherFx();
// Будьте осторожны: const resultB = await regularFunction(); // Здесь область видимости потеряна. },});
Чтобы решить эту проблему, просто оберните вашу regularFunction
в эффект:
const regularFunctionFx = createEffect(regularFunction);
Типы
Один стор
const $store: Store<T>;
const attachedFx: Effect<Params, Done, Fail> = attach({ source: $store, async effect(source, params: Params): Done | Promise<Done> {},});
Вам нужно явно указать только аргумент params
. Все остальные типы аргументов должны быть выведены автоматически. Также вы можете явно указать тип возвращаемого значения функции effect
.
Если вы хотите удалить любые аргументы из attachedFx
, просто удалите второй аргумент из функции effect
:
const attachedFx: Effect<void, void, Fail> = attach({ source: $store, async effect(source) {},});
Несколько сторов
Для подробностей ознакомьтесь с предыдущим разделом типов. Здесь та же логика.
// Пример пользовательского кода без явных объявлений типовconst $foo = createStore(100);const $bar = createStore("demo");
const attachedFx = attach({ source: { foo: $foo, bar: $bar }, async effect({ foo, bar }, { baz }: { baz: boolean }) { console.log("Попадание!", { foo, bar, baz }); },});
attachedFx({ baz: true });// => Попадание! { foo: 100, bar: "demo", baz: true }
Попробуйте в песочнице TypeScript
Пример
Пожалуйста, создайте pull request через ссылку “Edit this page”.
attach({effect, mapParams})
Создает эффект, который будет вызывать указанный эффект, преобразуя параметры с помощью функции mapParams
.
Формула
const attachedFx = attach({ effect: originalFx, mapParams,});
- Когда
attachedFx
вызывается, параметры передаются в функциюmapParams
, затем результат передается вoriginalFx
. - Когда
originalFx
завершается,attachedFx
завершается с тем же состоянием (успех/ошибка). - Если
mapParams
выбрасывает исключение,attachedFx
завершается с ошибкой какattachedFx.fail
. НоoriginalFx
не будет вызван.
Аргументы
effect
(Effect): Обернутый эффект.mapParams
((newParams) => effectParams
): Функция, которая принимает новые параметры и преобразует их в параметры для обернутогоeffect
. Работает аналогично event.prepend. Ошибки в функцииmapParams
приведут к завершению прикрепленного эффекта с ошибкой.
Возвращает
Effect: Новый эффект.
Типы
const originalFx: Effect<A, Done, Fail>;
const attachedFx: Effect<B, Done, Fail> = attach({ effect: originalFx, mapParams: (params: B): A {},});
mapParams
должна возвращать тот же тип, который принимает originalFx
в качестве параметров.
Если attachedFx
должен вызываться без аргументов, то params
можно безопасно удалить из mapParams
:
const attachedFx: Effect<void, Done, Fail> = attach({ effect: originalFx, mapParams: (): A {},});
Попробуйте в песочнице TypeScript
Но если функция mapParams
выбрасывает исключение, вам нужно самостоятельно проверять совместимость типов, так как TypeScript не поможет.
const attachedFx: Effect<void, Done, Fail> = attach({ effect: originalFx, mapParams: (): A { throw new AnyNonFailType(); // Это может быть несовместимо с типом `Fail`. },});
Примеры
Преобразование аргументов
import { createEffect, attach } from "effector";
const originalFx = createEffect((a: { input: number }) => a);
const attachedFx = attach({ effect: originalFx, mapParams(a: number) { return { input: a * 100 }; },});
originalFx.watch((params) => console.log("originalFx started", params));
attachedFx(1);// => originalFx { input: 100 }
Обработка исключений
import { createEffect, attach } from "effector";
const originalFx = createEffect((a: { a: number }) => a);
const attachedFx = attach({ effect: originalFx, mapParams(a: number) { throw new Error("custom error"); return { a }; },});
attachedFx.failData.watch((error) => console.log("attachedFx.failData", error));
attachedFx(1);// => attachedFx.failData// => Error: custom error
attach({source, mapParams, effect})
Создает эффект, который будет читать значения из source
стора, передавать их с параметрами в функцию mapParams
, а затем вызывать effect
с результатом.
Формула
Этот вариант attach
работает аналогично attach({effect, mapParams}). Поэтому некоторые вещи опущены в этом разделе.
const attachedFx = attach({ source, mapParams, effect: originalFx,});
- Когда
attachedFx
вызывается, параметры передаются в функциюmapParams
вместе с данными изsource
, затем результат передается вoriginalFx
. - Когда
originalFx
завершается,attachedFx
завершается с тем же состоянием (успех/ошибка). - Если
mapParams
выбрасывает исключение,attachedFx
завершается с ошибкой какattachedFx.fail
. НоoriginalFx
не будет вызван.
Аргументы
source
(Store |{[key: string]: Store}
): Стор или объект с сторами, значения которых будут переданы во второй аргументmapParams
.mapParams
((newParams, values) => effectParams
): Функция, которая принимает новые параметры и текущее значениеsource
и объединяет их в параметры для обернутогоeffect
. Ошибки в функцииmapParams
приведут к завершению прикрепленного эффекта с ошибкой.effect
(Effect): Обернутый эффект.
Возвращает
Effect: Новый эффект.
Типы
Пожалуйста, создайте pull request через ссылку “Edit this page”.
Примеры
С фабрикой
import { createEffect, createStore } from "effector";
export const backendRequestFx = createEffect(async ({ token, data, resource }) => { return fetch(`https://example.com/api${resource}`, { method: "POST", headers: { Authorization: `Bearer ${token}`, }, body: JSON.stringify(data), });});
export const $requestsSent = createStore(0);
$requestsSent.on(backendRequestFx, (total) => total + 1);
import { attach, createStore } from "effector";
const $token = createStore("guest_token");
export const authorizedRequestFx = attach({ effect: backendRequestFx, source: $token, mapParams: ({ data, resource }, token) => ({ data, resource, token }),});
export function createRequest(resource) { return attach({ effect: authorizedRequestFx, mapParams: (data) => ({ data, resource }), });}
import { createRequest } from "./authorized";import { $requestsSent } from "./request";
const getUserFx = createRequest("/user");const getPostsFx = createRequest("/posts");
$requestsSent.watch((total) => { console.log(`Аналитика клиента: отправлено ${total} запросов`);});
const user = await getUserFx({ name: "alice" });/*POST https://example.com/api/user{"name": "alice"}Authorization: Bearer guest_token*/
// => Аналитика клиента: отправлено 1 запросов
const posts = await getPostsFx({ user: user.id });/*POST https://example.com/api/posts{"user": 18329}Authorization: Bearer guest_token*/
// => Аналитика клиента: отправлено 2 запросов
Чтобы фабрика работала корректно, добавьте путь к ./api/authorized
в опцию factories
для Babel плагина:
{ plugins: [ [ "effector/babel-plugin", { factories: ["src/path-to-your-entity/api/authorized"], }, ], ],}
Параметры
attach()
также принимает дополнительные параметры, которые можно использовать при необходимости.
name
attach({ name: string });
Позволяет явно задать имя созданного прикрепленного эффекта:
import { attach } from "effector";
const attachedFx = attach({ name: "anotherUsefulName", source: $store, async effect(source, params: Type) { // ... },});
attachedFx.shortName; // "anotherUsefulName"
Этот параметр доступен в любом варианте attach
.
domain
attach({ domain: Domain });
Позволяет создать эффект внутри указанного домена.
Примечание: это свойство может использоваться только с обычной функцией
effect
.
import { createDomain, createStore, attach } from "effector";
const reportErrors = createDomain();const $counter = createStore(0);
const attachedFx = attach({ domain: reportErrors, source: $counter, async effect(counter) { // ... },});
Документация на английском языке - самая актуальная, поскольку её пишет и обновляет команда effector. Перевод документации на другие языки осуществляется сообществом по мере наличия сил и желания.
Помните, что переведенные статьи могут быть неактуальными, поэтому для получения наиболее точной и актуальной информации рекомендуем использовать оригинальную англоязычную версию документации.