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”.
Примеры
С фабрикой
// ./api/request.ts
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);
// ./api/authorized.ts
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 }),
});
}
// ./api/index.ts
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 плагина:
// .babelrc
{
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. Перевод документации на другие языки осуществляется сообществом по мере наличия сил и желания.
Помните, что переведенные статьи могут быть неактуальными, поэтому для получения наиболее точной и актуальной информации рекомендуем использовать оригинальную англоязычную версию документации.