import { attach } from "effector";
Начиная с

effector 20.13.0.

С версии effector 22.4.0 можно проверить, создан ли эффект через метод attachis.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

Пример

TBD

Пожалуйста, создайте 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: Новый эффект.

Типы

TBD

Пожалуйста, создайте 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. Перевод документации на другие языки осуществляется сообществом по мере наличия сил и желания.

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

Соавторы