Effector.dev Documentation
---
# FAQ
## Часто задаваемые вопросы про Effector
### Зачем нужны плагины для babel/swc для SSR?
Плагины эффектора вставляют в код специальные метки - SID, это позволяет автоматизировать сериализацию и десериализацию сторов, так что юзерам не требуется думать о ручной сериализации. Более глубокое объяснение в статье про sid.
### Зачем нам нужно давать имена событиям, эффектам и т.д.?
Это поможет в будущем, при разработке инструментов Effector Devtools, и сейчас используется в [плейграунде](https://share.effector.dev) на боковой панели слева.\
Если вы не хотите этого делать, вы можете использовать [Babel плагин](https://www.npmjs.com/package/@effector/babel-plugin). Он автоматически сгенерирует имя для событий и эффектов из имени переменной.
# Изолированный контекст
import Tabs from "@components/Tabs/Tabs.astro";
import TabItem from "@components/Tabs/TabItem.astro";
import SideBySide from "@components/SideBySide/SideBySide.astro";
## Изолированный контекст
С помощью скоупов вы можете создать изолированный экземпляр всего приложения, который содержит независимую копию всех юнитов (включая их связи), а также базовые методы для работы с ними:
```ts "fork" "allSettled"
import { fork, allSettled, createStore, createEvent } from "effector";
// Создаем новый скоуп
const scope = fork();
const $counter = createStore(0);
const increment = createEvent();
$counter.on(increment, (state) => state + 1);
// Запускаем событие и дожидаемся всей цепочки выполнения
await allSettled(increment, { scope });
console.log(scope.getState($counter)); // 1
console.log($counter.getState()); // 0 - оригинальный стор остается без изменений
```
С помощью fork мы создаем новый скоуп, а с помощью allSettled — запускаем цепочку событий внутри указанного скоупа и дожидаемся ее завершения.
> INFO Независимость скоупов:
>
> Не существует механизма для обмена данными между скоупами; каждый экземпляр полностью изолирован и работает самостоятельно.
### Зачем нужен cкоуп?
В effector все состояние хранится глобально. В клиентском приложении (SPA) это не проблема: каждый пользователь получает собственный экземпляр кода и работает со своим состоянием. Но при серверном рендеринге (SSR) или параллельном тестировании глобальное состояние становится проблемой: данные одного запроса или теста могут “протечь” в другой. Поэтому нам необходим скоуп.
* **SSR** — сервер работает как единый процесс и обслуживает запросы множества пользователей. Для каждого запроса можно создать скоуп, который изолирует данные от глобального контекста Effector и предотвращает утечку состояния одного пользователя в запрос другого.
* **Тестирование** — при параллельном запуске тестов возможны гонки данных и коллизии состояний. Скоуп позволяет каждому тесту выполняться со своим собственным изолированным состоянием.
У нас есть подробные гайды по работе с серверным рендерингом (SSR) и тестировании, а здесь мы сосредоточимся на основных принципах работы со скоупом, его правилах и способах избежать распространенных ошибок.
### Правила работы со скоупом
Для корректной работы со скоупом имеется ряд правил, чтобы избежать потери скоупа:
#### Вызов эффектов и промисов
Для обработчиков эффектов, которые вызывают другие эффекты, убедитесь, что вы вызываете только эффекты, а не обычные асинхронные функции. Кроме того, вызовы эффектов должны быть ожидаемыми (`awaited`).
Императивные вызовы эффектов в этом плане безопасны, потому что effector запоминает скоуп в котором начинался императивный вызов эффекта и при завершении вызова восстанавливает его обратно, что позволяет сделать ещё один вызов подряд.
Можно вызывать методы `Promise.all([fx1(), fx2()])` и прочие из стандартного api javascript, потому что в этих случаях вызовы эффектов по прежнему происходят синхронно и скоуп безопасно сохраняется.
Count: {count}
> ); }; const myScope = fork({ values: [[$count, 42]], }); render({text}
} ``` # useEvent > ERROR Устаревшее API : > > Рекомендуется использовать хук useUnit. Реакт-хук, который привязывает событие к текущему scope для использования в обработчиках событий Используется с серверным рендерингом и в тестировании, импортируется из `effector-react/scope` ### *useEvent(unit)* Привязывает юнит к скоупу компонента #### Формула ```ts declare const event: EventCount: {count}
> ); }; const scope = fork(); ReactDOM.render(Count: {count}
> ); }; const scope = fork(); ReactDOM.render(Count: {count}
> ); }; const scope = fork(); ReactDOM.render(Count: {count}
> ); }; const scope = fork(); render( () => (Count: {count}
> ); }; const scope = fork(); render( () => ({text}
} ``` # useGate ```ts import { useGate } from "effector-solid"; ``` Function for passing data to . ## Methods ### `useGate(Gate, props)` #### Formulae ```ts useGate(Gate: GateCount: {count()}
> ); }; const scope = fork(); render( () => (Count: {count()}
> ); }; const scope = fork(); render( () => ({{ user.name }}
...Count: {{ count }}
``` #### `useUnit(shape)` ##### Arguments 1. `shape` Object or array of ( or or ): Every unit will be processed by `useUnit` and returned as a reactive value in case of or as a function to pass to event handlers in case of or . ##### Returns (Object or Array): * if or : functions with the same names or keys as argument to pass to event handlers. Will trigger given unit in current . * if : reactive value of given with the same names or keys as argument. ##### Examples ###### Basic Usage ```js // model.js import { createEvent, createStore, fork } from "effector"; const incremented = createEvent(); const $count = createStore(0); $count.on(incremented, (count) => count + 1); ``` ```html // App.vueCount: {{ count }}
; // созданное событие-команда } = createApi( /*store*/ $store, /*handlers*/ { event1: /*handler*/ (state: T, data: S) => T, event2: /*handler*/ (state: T, data: Q) => T, }, ); ``` #### Аргументы 1. **`store`**: Стор, чьим значением требуется управлять 2. **`handlers`**: Объект с функциями-обработчиками, на каждую функцию будет создано по событию **`handler`**: `(state: T, data: S) => T` Функция-обработчик, которая будет вычислять новое состояние `стора` на основе его предыдущего состояния и данных, отправленных в полученное событие-команду, должна быть **Аргументы** * **`state`**: Текущее состояние стора * **`data`**: Значение, с которым было вызвано событие **Возвращает** Новое значение для хранения в `сторе`. Если функция возвращает undefined или текущее состояние стора, то обновления не будет #### Возвращает Объект с событиями, по событию на каждый переданный обработчик ### Примеры #### Управление позицией игрока ```js import { createStore, createApi } from "effector"; const playerPosition = createStore(0); const api = createApi(playerPosition, { moveLeft: (pos, n) => pos - n, moveRight: (pos, n) => pos + n, }); playerPosition.watch((pos) => { console.log("position", pos); }); // => position 0 api.moveRight(10); // => position 10 api.moveLeft(5); // => position 5 ``` Запустить пример # createDomain Метод для создания доменов ## Методы ```typescript createDomain(name?) ``` **Аргументы** 1. `name`? (*string*): имя домена **Возвращает** : Новый домен #### Пример ```js import { createDomain } from "effector"; const domain = createDomain(); // безымянный домен const httpDomain = createDomain("http"); // именованный домен const statusCodeChanged = httpDomain.createEvent(); const downloadFx = httpDomain.createEffect(); const apiDomain = httpDomain.createDomain(); // вложенный домен const $data = httpDomain.createStore({ status: -1 }); ``` Запустить пример # createEffect ## createEffect ```ts import { createEffect } from "effector"; const effectFx = createEffect(); ``` Метод для создания эффектов. Возвращает новый эффект. ### Способы создания эффектов Метод `createEffect` поддерживает несколько способов создания эффектов: 1. С обработчиком - это самый простой способ. 2. С конфигурацией. 3. А также без обработчика, его можно будет задать позже с помощью метода .use(handler). #### С обработчиком * **Тип** ```ts createEffect( handler: (params: Params) => Done | Promise ``` * **Пример** ```ts import { createEffect } from "effector"; const fetchUserReposFx = createEffect(async ({ name }) => { const url = `https://api.github.com/users/${name}/repos`; const req = await fetch(url); return req.json(); }); fetchUserReposFx.done.watch(({ params, result }) => { console.log(result); }); await fetchUserReposFx({ name: "zerobias" }); ``` #### С конфигурацией Поле `name` используется для улучшения сообщений об ошибках и отладки. * **Тип** ```ts export function createEffect (config: { name?: string; handler?: (params: Params) => Promise ; ``` * **Пример** ```ts import { createEffect } from "effector"; const fetchUserReposFx = createEffect({ name: "fetch user repositories", async handler({ name }) { const url = `https://api.github.com/users/${name}/repos`; const req = await fetch(url); return req.json(); }, }); await fetchUserReposFx({ name: "zerobias" }); ``` #### Без обработчика Чаще всего используется для тестов. Более подробная информация. > WARNING use - это антипаттерн: > > Старайтесь не использовать `.use()`, так как это является антипаттерном и ухудшает вывод типов. * **Пример** ```ts import { createEffect } from "effector"; const fetchUserReposFx = createEffect(); fetchUserReposFx.use(async ({ name }) => { const url = `https://api.github.com/users/${name}/repos`; const req = await fetch(url); return req.json(); }); await fetchUserReposFx({ name: "zerobias" }); ``` ### Примеры * **Изменение состояния по завершению эффекта**: ```ts import { createStore, createEffect } from "effector"; interface Repo { // ... } const $repos = createStore ([]); const fetchUserReposFx = createEffect(async (name: string) => { const url = `https://api.github.com/users/${name}/repos`; const req = await fetch(url); return req.json(); }); $repos.on(fetchUserReposFx.doneData, (_, repos) => repos); $repos.watch((repos) => { console.log(`${repos.length} repos`); }); // => 0 репозиториев await fetchUserReposFx("zerobias"); // => 26 репозиториев ``` Запустить пример * **Наблюдение за состоянием эффекта**: ```js import { createEffect } from "effector"; const fetchUserReposFx = createEffect(async ({ name }) => { const url = `https://api.github.com/users/${name}/repos`; const req = await fetch(url); return req.json(); }); fetchUserReposFx.pending.watch((pending) => { console.log(`effect is pending?: ${pending ? "yes" : "no"}`); }); fetchUserReposFx.done.watch(({ params, result }) => { console.log(params); // {name: 'zerobias'} console.log(result); // разрешенное значение, результат }); fetchUserReposFx.fail.watch(({ params, error }) => { console.error(params); // {name: 'zerobias'} console.error(error); // отклоненное значение, ошибка }); fetchUserReposFx.finally.watch(({ params, status, result, error }) => { console.log(params); // {name: 'zerobias'} console.log(`handler status: ${status}`); if (error) { console.log("handler rejected", error); } else { console.log("handler resolved", result); } }); await fetchUserReposFx({ name: "zerobias" }); ``` Запустить пример ### Основные ошибки Ниже приведен список возможных ошибок, с которыми вы можете столкнуться при работе с эффектами: * no handler used in \[effect name] ### Связанные API и статьи * **API** * Effect API - Описание эффектов, его методов и свойств * sample - Ключевой оператор для построения связей между юнитами * attach - Создает новые эффекты на основе других эффектов * **Статьи** * Работа с эффектами * Как типизировать эффекты и не только * Гайд по тестированию эффектов и других юнитов # createEvent ## createEvent ```ts import { createEvent } from "effector"; const event = createEvent(); ``` Метод для создания [событий][eventApi]. ### Формула ```ts createEvent (eventName?: string): EventCallable createEvent (config: { name?: string sid?: string domain?: Domain }): EventCallable ``` * **Аргументы** * `eventName`: Опциональный аргумент. Имя события для отладки. * `config`: Опциональный аргумент. Объект конфигурации. * `name`: Имя события. * `sid`: Стабильный идентификатор для SSR. * `domain`: Домен для события. * **Возвращаемое значение** Возвращает новое вызываемое [событие][eventTypes]. ### Примеры Обновление состояния с помощью вызова события: ```js import { createStore, createEvent } from "effector"; const addNumber = createEvent(); const $counter = createStore(0); $counter.on(addNumber, (state, number) => state + number); $counter.watch((state) => { console.log("state", state); }); // => 0 addNumber(10); // => 10 addNumber(10); // => 20 addNumber(10); // => 30 ``` Запустить пример Мы создали событие `addNumber` и стор `$counter`, после чего подписались на обновления стора.
Обратите внимание на вызов функции `addNumber(10)`. Всякий раз, когда вы будете вызывать `addNumber(10)`, вы можете посмотреть в консоль и увидеть, как меняется состояние. Обработка данных с помощью производных событий: ```js import { createEvent } from "effector"; const extractPartOfArray = createEvent(); const array = extractPartOfArray.map((arr) => arr.slice(2)); array.watch((part) => { console.log(part); }); extractPartOfArray([1, 2, 3, 4, 5, 6]); // => [3, 4, 5, 6] ``` Запустить пример ### Основные ошибки Ниже приведён список возможных ошибок, с которыми вы можете столкнуться при работе с событиями: * call of derived event is not supported, use createEvent instead * unit call from pure function is not supported, use operators like sample instead ### Связанные API и статьи * **API** * [`Event API`][eventApi] - API стора, его методы, свойства и описание * [`createApi`][createApi] - Создание набора событий для стора * [`merge`][merge] - Метод для объединения массива юнитов в одно новое событие * [`sample`][sample] - Связывание событий с другими юнитами * **Статьи** * [Как работать с событиями][eventGuide] * [Как мыслить в effector и почему события важны][mindset] * [Гайд по типизации событий и других юнитов][typescript] [eventApi]: /ru/api/effector/Event [eventTypes]: /ru/api/effector/Event#event-types [merge]: /ru/api/effector/merge [eventGuide]: /ru/essentials/events [mindset]: /ru/resources/mindset [mindset]: /ru/resources/mindset [typescript]: /ru/essentials/typescript [sample]: /ru/api/effector/sample [createApi]: /ru/api/effector/createApi # createStore ## createStore ```ts import { createStore } from "effector"; const $store = createStore(); ``` Метод для создания [стора][storeApi]. ### Формула ```ts createStore( defaultState: State, // Исходное состояние стора config?: { // Объект конфигурации с дополнительными опциями skipVoid?: boolean; // Контролирует обновления со значением undefined name?: string; // Имя стора для отладки sid?: string // Стабильный идентификатор для SSR updateFilter?: (update: State, current: State) => boolean // Функция фильтрации обновлений serialize?: // Конфигурация сериализации для SSR | 'ignore' | { write: (state: State) => SerializedState read: (json: SerializedState) => State } domain?: Domain; // Домен, к которому принадлежит стор }, ): StoreWritable``` * **Аргументы** 1. **`defaultState`**: Исходное состояние 2. **`config`**: Опциональный объект конфигурации * **`skipVoid`**: Опциональный аргумент. Определяет пропускает ли [стор][storeApi] `undefined` значения. По умолчанию `true`. В случае если передать в стор, у которого `skipVoid:true`, значение `undefined`, тогда вы получите [ошибку в консоль][storeUndefinedError].
* **`name`**: Опциональный аргумент. Имя стора. [Babel-plugin][babel] может определить его из имени переменной стора, если имя не передано явно в конфигурации.
* **`sid`**: Опциональный аргумент. Уникальный идентификатор стора. [Он используется для различения сторов между разными окружениями][storeSid]. При использовании [Babel-plugin][babel] проставляется автоматически.
* **`updateFilter`**: Опциональный аргумент. [Чистая функция][pureFn], которая предотвращает обновление стора, если она возвращает `false`. Следует использовать для случаев, когда стандартного запрета на обновление (если значение, которое предполагается записать в стор, равняется `undefined` или текущему значению стора) недостаточно. Если вызывать юниты внутри, то можно столкнуться с [ошибкой][unitCallError].
* **`serialize`**: Опциональный аргумент отвечающий за сериализацию стора. * `'ignore'`: исключает стор из сериализации при вызовах [serialize][serialize]. * Объект с методами `write` и `read` для кастомной сериализации. `write` вызывается при вызове serialize и приводит состояние стор к JSON-значению – примитив или простой объект/массив. `read` вызывается при fork, если предоставленные `values` – результат вызова [serialize][serialize]. * **Возвращаемое значение** Возвращает новый [стор][storeApi]. ### Примеры Базовое использование стора: ```js import { createEvent, createStore } from "effector"; const addTodo = createEvent(); const clearTodos = createEvent(); const $todos = createStore([]) .on(addTodo, (todos, newTodo) => [...todos, newTodo]) .reset(clearTodos); const $selectedTodos = $todos.map((todos) => { return todos.filter((todo) => !!todo.selected); }); $todos.watch((todos) => { console.log("todos", todos); }); ``` Запустить пример Пример с кастомной конфигурацией `serialize`: ```ts import { createEvent, createStore, serialize, fork, allSettled } from "effector"; const saveDate = createEvent(); const $date = createStore(null, { // Объект Date автоматически приводится в строку ISO-даты при вызове JSON.stringify // но не приводится обратно к Date при вызове JSON.parse – результатом будет та же строка ISO-даты // Это приведет к расхождению состояния стора при гидрации состояния на клиенте при серверном рендеринге // // Кастомная конфигурация `serialize` решает эту проблему serialize: { write: (dateOrNull) => (dateOrNull ? dateOrNull.toISOString() : dateOrNull), read: (isoStringOrNull) => (isoStringOrNull ? new Date(isoStringOrNull) : isoStringOrNull), }, }).on(saveDate, (_, p) => p); const serverScope = fork(); await allSettled(saveDate, { scope: serverScope, params: new Date() }); const serverValues = serialize(serverScope); // `serialize.write` стор `$date` был вызван console.log(serverValues); // => { nq1e2rb: "2022-11-05T15:38:53.108Z" } // Объект Date из стора сохранен как ISO-дата const clientScope = fork({ values: serverValues }); // `serialize.read` стор `$date` был вызван const currentDate = clientScope.getState($date); console.log(currentDate); // => Date 11/5/2022, 10:40:13 PM // Строка ISO-даты приведена обратно к объекту Date ``` Запустить пример ### Типичные ошибки Ниже приведен список возможных ошибок, с которыми вы можете столкнуться при работе со сторами: * [`store: undefined is used to skip updates. To allow undefined as a value provide explicit { skipVoid: false } option`][storeUndefinedError]. * [`serialize: One or more stores dont have sids, their values are omitted`][serializeError]. * [`unit call from pure function is not supported, use operators like sample instead`][unitCallError]. ### Связанные API и статьи * **API** * [`Store API`][storeApi] - API стора, его методы, свойства и описание * [`createApi`][createApi] - Создание набора событий для стора * [`combine`][combine] - Создание нового стора на основе других сторов * [`sample`][sample] - Связывание сторов с другими юнитами * **Статьи** * [Как управлять состоянием][storeGuide] * [Гайд по работе с SSR][ssr] * [Что такое SID и зачем они нужны сторам][storeSid] * [Как типизировать сторы и другие юниты][typescript] [storeApi]: /ru/api/effector/Store [storeUndefinedError]: /ru/guides/troubleshooting#store-undefined [storeSid]: /ru/explanation/sids [ssr]: /ru/guides/server-side-rendering [storeGuide]: /ru/essentials/manage-states [combine]: /ru/api/effector/combine [sample]: /ru/api/effector/sample [createApi]: /ru/api/effector/createApi [serialize]: /ru/api/effector/serialize [typescript]: /ru/essentials/typescript [babel]: /ru/api/effector/babel-plugin [pureFn]: /ru/explanation/glossary/#purity [unitCallError]: /ru/guides/troubleshooting#unit-call-from-pure-not-supported [serializeError]: /ru/guides/troubleshooting/#store-without-sid # createWatch Создает подписку на юнит (store, ивент или эффект). ## Методы ```ts createWatch (config: { unit: Unit fn: (payload: T) => void scope?: Scope }): Subscription ``` **Аргументы** 1. `config` (*Object*): Конфигурация * `unit` (*Unit*): Целевой юнит (store, ивент или эффект), за которым нужно наблюдать * `fn` (*Function*): Функция, которая будет вызываться при каждом обновлении юнита. Первым аргументом получает содержимое обновления. * `scope` (): Опциональный скоуп. Если передан, то функция будет вызываться только при обновлении юнита именно на этом скоупе. **Возвращает** : Функция отмены подписки ##### Пример (со скоупом) ```js import { createWatch, createEvent, fork, allSettled } from "effector"; const changeName = createEvent(); const scope = fork(); const unwatch = createWatch({ unit: changeName, scope, fn: console.log }); await allSettled(changeName, { scope, params: "Иван" }); // output: Иван changeName("Иван"); // no output ``` ##### Пример (без скоупа) ```js import { createWatch, createEvent, fork, allSettled } from "effector"; const changeName = createEvent(); const scope = fork(); const unwatch = createWatch({ unit: changeName, fn: console.log }); await allSettled(changeName, { scope, params: "Иван" }); // output: Иван changeName("Иван"); // output: Иван ``` # debug traces ## Debug Trace import ```ts import "effector/enable_debug_traces"; ``` A special import that enables detailed traces for difficult-to-debug errors, such as a Store missing a proper SID during Scope serialization. > WARNING Performance cost: > > Debug traces work by capturing additional information when Stores and Events are created. > This introduces a performance overhead during module initialization. > > We do not recommend using this API in production environments. To enable debug traces, add `import "effector/enable_debug_traces"` to the entrypoint of your bundle, like this: ```ts // src/index.ts import "effector/enable_debug_traces"; // ...rest of your code ``` ### When to use it If you encounter an error that can be diagnosed with this API, you will see a recommendation in the console: `Add "import 'effector/enable_debug_traces'" to your code entry module to see full stack traces`. Don't forget to remove this import once the issue has been resolved. # fork API [scopeApi]: /ru/api/effector/Scope [domainApi]: /ru/api/effector/Domain [serializeApi]: /ru/api/effector/serialize [allSettledApi]: /ru/api/effector/allSettled [hydrateApi]: /ru/api/effector/hydrate [sampleApi]: /ru/api/effector/sample [storeApi]: /ru/api/effector/Store [effectApi]: /ru/api/effector/Effect ## `fork` API ```ts import { fork, type Scope } from "effector"; ``` Метод `fork` создает изолированный [скоуп][scopeApi] приложения. Он нужен для SSR, тестирования и локальной изоляции состояния — вы запускаете вычисления в копии без влияния на глобальные юниты. ### Алгоритм работы 1. Вы вызываете `fork`, получая новый [скоуп][scopeApi]. 2. Если переданы `values` или `handlers`, они применяются при создании этого [скоупа][scopeApi]. 3. Юниты, вызванные с этим [скоупом][scopeApi], работают с изолированными значениями и обработчиками. 4. Состояние читается через `scope.getState(store)` или сериализуется через [`serialize`][serializeApi]. ### Виды конфигураций `fork` | Форма| Описание | | ------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- | | fork() | Создает новый чистый [скоуп][scopeApi] без предзаполненных данных и переопределений обработчиков. | | fork({ values?, handlers? }) | Создает [скоуп][scopeApi] с начальными значениями сторов и пользовательскими обработчиками эффектов. | | fork(domain, options?) | Устаревшая форма, требующая `domain`. Используйте `fork({ values?, handlers? })`, если нет зависимости от старой сигнатуры. | ### Конфигурации #### `fork()` Создает новый прикладной [скоуп][scopeApi] без дополнительных настроек. * **Формула** ```ts fork(): Scope ``` * **Тип** ```ts type SerializedState = Record; type StorePair = [StoreWritable , T]; export function fork(config?: { values?: StorePair [] | SerializedState; handlers?: Array<[Effect , Function]>; }): Scope; ``` * **Особенности** * Возвращает новый [скоуп][scopeApi] без предзаполненных сторов и без замены обработчиков. * Подходит, когда данные и побочные эффекты должны остаться неизменными (например, первый рендер на сервере). * **Примеры** ```ts import { createStore, createEvent, fork, allSettled } from "effector"; const inc = createEvent(); const dec = createEvent(); const $counter = createStore(0); $counter.on(inc, (value) => value + 1); $counter.on(dec, (value) => value - 1); const scopeA = fork(); const scopeB = fork(); await allSettled(inc, { scope: scopeA }); await allSettled(dec, { scope: scopeB }); console.log($counter.getState()); // => 0 console.log(scopeA.getState($counter)); // => 1 console.log(scopeB.getState($counter)); // => -1 ``` * **Возвращаемое значение** Новый [скоуп][scopeApi]. #### `fork({ values?, handlers? })` Позволяет задать начальные значения для сторов и переопределить обработчики эффектов внутри [скоупа][scopeApi]. * **Формула** ```ts fork({ values?, // например [[$store, value], ...] или сериализованный объект handlers?, // например [[effect, handler], ...] }): Scope ``` * **Тип** ```ts type SerializedState = Record ; type StorePair = [StoreWritable , T]; export function fork(config?: { values?: StorePair [] | SerializedState; handlers?: Array<[Effect , Function]>; }): Scope; ``` * **Особенности** * `values` может быть массивом кортежей `[$store, value]` или объектом сериализованного состояния (обычно результат [`serialize`][serializeApi]); предпочтителен массив кортежей. * `handlers` принимает только массив кортежей, замены работают только в пределах созданного [скоупа][scopeApi]. * Значения и обработчики применяются один раз при создании [скоупа][scopeApi] и не обновляются автоматически. * **Примеры** Задание начального состояния и подмена обработчика в тесте: ```ts import { createEffect, createStore, fork, allSettled } from "effector"; const fetchFriendsFx = createEffect<{ limit: number }, string[]>(async ({ limit }) => { return []; }); const $user = createStore("guest"); const $friends = createStore ([]); $friends.on(fetchFriendsFx.doneData, (_, result) => result); const testScope = fork({ values: [[$user, "alice"]], handlers: [[fetchFriendsFx, () => ["bob", "carol"]]], }); await allSettled(fetchFriendsFx, { scope: testScope, params: { limit: 10 }, }); console.log(testScope.getState($friends)); // => ['bob', 'carol'] ``` Создание [скоупа][scopeApi] с сериализованным состоянием: ```ts import { fork } from "effector"; const serialized = { userSid: "alice", ageSid: 21, }; const scope = fork({ values: serialized }); ``` * **Возвращаемое значение** Новый [скоуп][scopeApi] с примененными `values` и `handlers`. #### `fork(domain, options?)` Устаревшая форма, требующая [domain][domainApi]; актуальна только для совместимости со старым кодом. > ERROR Устарело: > > Используйте `fork({ values?, handlers? })`, так как `fork` отслеживает юниты автоматически без передачи `domain`. * **Формула** ```ts fork(domain, { values?, // например [[$store, value], ...] или сериализованный объект handlers?, // например [[effect, handler], ...] }): Scope ``` * **Тип** ```ts type SerializedState = Record ; export function fork( domain: Domain, config?: { values?: SerializedState | Array<[StoreWritable , any]>; handlers?: Array<[Effect , Function]>; }, ): Scope; ``` * **Особенности** * Передача `domain` нужна только в проектах, где код еще зависит от старой сигнатуры. * Допустимые форматы `values` и `handlers` совпадают с конфигурацией без `domain`. * **Пример** ```ts import { createDomain, createStore, fork } from "effector"; const app = createDomain(); const $flag = app.createStore(false); const scope = fork(app, { values: [[$flag, true]], }); console.log(scope.getState($flag)); // => true ``` * **Возвращаемое значение** Новый [скоуп][scopeApi], привязанный к указанному [domain][domainApi]. ### Связанные API и статьи * **API** * Scope — структура изолированного состояния * allSettled — выполнение эффекта внутри [скоупа][scopeApi] с ожиданием завершения * serialize — сериализация состояния [скоупа][scopeApi] * hydrate — восстановление состояния в [скоупе][scopeApi] * **Статьи** * SSR и работа со скоупом * Тестирование с isolated скоупом * Работа со скоупом и потеря контекста # forward > ERROR Устаревшее АПИ: > > Начиная с версии [effector 23.0.0](https://changelog.effector.dev/#effector-23-0-0). > > Используйте sample вместо `forward`. Метод для создания связи между юнитами в декларативной форме. Отправляет обновления из одного набора юнитов в другой ## Методы ### Формула ```ts declare const a: Event declare const fxA: Effect declare const $a: Store declare const b: Event declare const fxB: Effect declare const $b: Store forward({from: a, to: b}) forward({ from: fxA, to: [b, fxB, $b] }) forward({ from: [a, fxA, $a], to: fxB }) forward({ from: [a, fxA, $a], to: [b, fxB, $b] }) -> Subscription ``` ``` from -> to ``` #### Аргументы 1. **`config`**: Объект конфигурации * **`from`**: Юнит или массив юнитов **Разновидности**: * **событие или эффект**: срабатывание этого события/эффекта будет запускать юниты `to` * **стор**: обновление этого стора будет запускать юниты `to` * **массив юнитов**: срабатывание любого из юнитов будет запускать юниты `to` * **`to`**: Юнит или массив юнитов **Разновидности**: * **событие или эффект**: при срабатывании `from` будет вызван данный юнит * **стор**: при срабатывании `from` состояние юнита будет обновлено * **массив юнитов**: при срабатывании `from` будут запущены все юниты #### Возвращает Subscription: Функция отмены подписки, после её вызова реактивная связь между `from` и `to` разрушается > INFO: > > Массивы юнитов поддерживаются с [effector 20.6.0](https://changelog.effector.dev/#effector-20-6-0) Для наилучшей типизации при использовании массивов юнитов, типы значений должны совпадать либо быть явно приведены к общему базису ### Примеры #### Сохранение в сторе данных из события ```js import { createStore, createEvent, forward } from "effector"; const $store = createStore(1); const event = createEvent(); forward({ from: event, to: $store, }); $store.watch((state) => console.log("store changed: ", state)); // => store changed: 1 event(200); // => store changed: 200 ``` Запустить пример #### Создание связи между массивами юнитов ```js import { createEvent, forward } from "effector"; const firstSource = createEvent(); const secondSource = createEvent(); const firstTarget = createEvent(); const secondTarget = createEvent(); forward({ from: [firstSource, secondSource], to: [firstTarget, secondTarget], }); firstTarget.watch((e) => console.log("first target", e)); secondTarget.watch((e) => console.log("second target", e)); firstSource("A"); // => first target A // => second target A secondSource("B"); // => first target B // => second target B ``` Запустить пример # fromObservable Создаёт событие, которое будет срабатывать при каждом обновлении переданного observable. Применяется для реализации взаимодействия с библиотеками на основе стримов, например `rxjs` и `most` Для обратного действия подписки стримов на юниты эффектора можно воспользоваться методами вроде `from` из `rxjs`: юниты эффектора распознаются как сущности, на которые можно подписаться ## Методы ### Формула ```ts function fromObservable(stream: Observable ): Event ; ``` #### Аргументы 1. **`observable`**: Observable #### Возвращает Новое событие ### Пример ```js import { interval } from "rxjs"; import { fromObservable } from "effector"; //emit value in sequence every 1 second const source = interval(1000); const event = fromObservable(source); //output: 0,1,2,3,4,5.... event.watch(console.log); ``` # guard > INFO: > > C effector 22.2.0 предпочтительнее использовать sample > INFO: > > Добавлен в effector 20.4.0 Метод для запуска юнитов по условию, условием может быть функция-предикат или отдельный стор. Позволяет описывать бизнес-правила независимо от других сущностей. Типичный вариант использования – когда необходимо запускать события лишь когда в определённом сторе значение равно `true`. Тем самым обеспечивается управление потоками данных без их смешивания ### Формула ```ts guard({clock?, source?, filter, target?}): target ``` > INFO: > > `clock` или `source` обязателен При срабатывании `clock`, после проверки `filter` на [истинность](https://developer.mozilla.org/ru/docs/Glossary/Truthy), вызывается `target` с данными из `source` * Если `clock` не передан, `guard` будет срабатывать при каждом обновлении `source` * Если `source` не передан, `target` будет вызван с данными из `clock` * Если `target` не передан, будет создано новое событие и возвращено в качестве результата * Если `filter` это стор, то его значение будет проверено на [истинность](https://developer.mozilla.org/ru/docs/Glossary/Truthy) * Если `filter` это функция-предикат, то она будет вызвана с данными из `source` и `clock`, а результат проверен на [истинность](https://developer.mozilla.org/ru/docs/Glossary/Truthy) > INFO: > > `clock` добавлен в effector 21.8.0 ### `guard({clock?, source?, filter, target?})` Основная запись метода **Аргументы** `params` (*Object*): Объект конфигурации * **`filter`**: Стор или функция-предикат **Разновидности**: * **стор**: `target` будет запущен только если в этом сторе [истинное значение](https://developer.mozilla.org/ru/docs/Glossary/Truthy) * **функция-предикат** `(source, clock) => boolean`: `target` будет запущен только если эта функция вернёт [истинное значение](https://developer.mozilla.org/ru/docs/Glossary/Truthy). Функция должна быть * **`clock?`**: Юнит или массив юнитов **Разновидности**: * **событие или эффект**: срабатывание этого события/эффекта, после проверки условия в `filter` будет запускать `target` * **стор**: обновление этого стора, после проверки условия в `filter` будет запускать `target` * **массив юнитов**: срабатывание любого из юнитов, после проверки условия в `filter` будет запускать `target`. Сокращение для вызова merge * **поле отсутствует**: `source` будет использоваться в качестве `clock` * **`source?`**: Юнит или массив/объект со сторами **Разновидности**: * **событие или эффект**: при срабатывании `clock` будет взято последнее значение с которым запускался этот юнит (перед этим он должен будет запуститься хотя бы раз) * **стор**: при срабатывании `clock` будет взято текущее значение этого стора * **массив или объект со сторами**: при срабатывании `clock` будут взяты текущие значения из заданных сторов, объединенных в объект или массив. Сокращение для вызова combine * **поле отсутствует**: `clock` будет использоваться в качестве `source` * **`target?`**: Юнит или массив юнитов **Разновидности**: * **событие или эффект**: при срабатывании `clock`, после проверки условия в `filter` будет вызван данный юнит * **стор**: при срабатывании `clock`, после проверки условия в `filter` состояние юнита будет обновлено * **массив юнитов**: при срабатывании `clock`, после проверки условия в `filter` будут запущены все юниты * **поле отсутствует**: новое событие будет создано и возвращено в результате вызова `guard` **Возвращает** , событие, которое будет срабатывать после проверки условия в `filter` #### Пример со стором в `filter` ```js import { createStore, createEffect, createEvent, guard } from "effector"; const clickRequest = createEvent(); const fetchRequest = createEffect((n) => new Promise((rs) => setTimeout(rs, 2500, n))); const clicks = createStore(0).on(clickRequest, (x) => x + 1); const requests = createStore(0).on(fetchRequest, (x) => x + 1); const isIdle = fetchRequest.pending.map((pending) => !pending); /* 1. при срабатывании clickRequest 2. если значение isIdle равно true 3. прочитать значение из clicks 4. и вызвать с ним эффект fetchRequest */ guard({ clock: clickRequest /* 1 */, filter: isIdle /* 2 */, source: clicks /* 3 */, target: fetchRequest /* 4 */, }); ``` Пример rate limiting #### Пример с функцией-предикатом в `filter` ```js import { createEffect, createEvent, guard } from "effector"; const searchUser = createEffect(); const submitForm = createEvent(); guard({ source: submitForm, filter: (user) => user.length > 0, target: searchUser, }); submitForm(""); // ничего не произошло submitForm("alice"); // ~> searchUser('alice') ``` Запустить пример ### `guard(source, {filter})` Альтернативная запись метода **Аргументы** * **`source`**: Юнит * **`filter`**: Стор или функция-предикат **Разновидности**: * **стор**: `target` будет запущен только если в этом сторе [истинное значение](https://developer.mozilla.org/ru/docs/Glossary/Truthy) * **функция-предикат** `(source) => boolean`: `target` будет запущен только если эта функция вернёт [истинное значение](https://developer.mozilla.org/ru/docs/Glossary/Truthy). Функция должна быть ##### Пример со стором в `filter` ```js import { createEvent, createStore, createApi, guard } from "effector"; const trigger = createEvent(); const $unlocked = createStore(true); const { lock, unlock } = createApi($unlocked, { lock: () => false, unlock: () => true, }); const target = guard(trigger, { filter: $unlocked, }); target.watch(console.log); trigger("A"); lock(); trigger("B"); // ничего не произошло unlock(); trigger("C"); ``` Запустить пример ##### Пример с функцией-предикатом в `filter` ```js import { createEvent, guard } from "effector"; const source = createEvent(); const target = guard(source, { filter: (x) => x > 0, }); target.watch(() => { console.log("target вызван"); }); source(0); // ничего не произошло source(1); // target вызван ``` Запустить пример # hydrate ```ts import { hydrate } from "effector"; ``` Сопутствующий метод для . Гидрирует предоставленные значения в соответствующие сторы в рамках предоставленного домена или скоупа. Основная цель — гидрация состояния приложения на стороне клиента после SSR (Server-Side Rendering). ## Методы #### `hydrate(domainOrScope, { values })` > WARNING Важно: > > Необходимо убедиться, что стор создан заранее, иначе гидрация может завершиться неудачей. Это может произойти, если вы разделяете скрипты инициализации/гидрации сторов от их создания. ##### Формула ```ts hydrate(domainOrScope: Domain | Scope, { values: Map , any> | {[sid: string]: any} }): void ``` ##### Аргументы (methods-hydrate-domainOrScope-values-arguments) 1. **`domainOrScope`**: домен или область видимости, который будет заполнен предоставленными `values`. 2. **`values`**: отображение из sid (идентификаторов сторов) в значения сторов или `Map`, где ключи — это объекты сторов, а значения содержат начальное значение стора. ##### Возвращает `void` ##### Примеры Заполнение стора предопределенным значением: ```js import { createStore, createDomain, fork, serialize, hydrate } from "effector"; const domain = createDomain(); const $store = domain.createStore(0); hydrate(domain, { values: { [$store.sid]: 42, }, }); console.log($store.getState()); // 42 ``` Запустить пример # effector ## Effector API Перечень методов API, по группам: ### Типы юнитов * Event\ * Effect\ * Store\ * Domain * Scope ### Создание юнитов * createEvent() * createStore(default) * createEffect(handler) * createDomain() ### Основные методы библиотеки * combine(...stores, f) * attach({effect, mapParams?, source?}) * sample({clock, source, fn, target}) * merge(\[eventA, eventB]) * split(event, cases) * createApi(store, api) ### Fork API * fork() * serialize(scope) * allSettled(unit, { scope }) * scopeBind(event) * hydrate(domain) ### Плагины для компилятора * effector/babel-plugin * @effector-swc-plugin ### Служебные функции * is * fromObservable(observable) ### Низкоуровневый API * clearNode() * withRegion() * launch() * inspect() ### Import Map Пакет `effector` предоставляет несколько дополнительных модулей, которые могут быть полезны в различных сценариях: * effector/compat * effector/inspect * effector/babel-plugin ### Устаревшие методы * forward({from, to}) * guard({source, filter, target}) # inspect ```ts import { inspect } from "effector/inspect"; ``` Специальные методы API, предназначенные для обработки сценариев отладки и мониторинга, не предоставляя слишком много доступа к внутренностям вашего приложения. Полезны для создания девтулз, мониторинга и наблюдения в production. ## Inspect API Позволяет отслеживать любые вычисления, происходящие в ядре effector. ### `inspect()` #### Пример ```ts import { inspect, type Message } from "effector/inspect"; import { someEvent } from "./app-code"; function logInspectMessage(m: Message) { const { name, value, kind } = m; return console.log(`[${kind}] ${name} ${value}`); } inspect({ fn: (m) => { logInspectMessage(m); }, }); someEvent(42); // выведет что-то вроде // [event] someEvent 42 // [on] 42 // [store] $count 1337 // ☝️ допустим, что редьюсер добавляет 1295 к предоставленному числу // // и так далее, любые триггеры ``` Scope ограничивает область, в которой можно отслеживать вычисления. Если scope не предоставлен — будут отслеживаться вычисления вне scope. ```ts import { fork, allSettled } from "effector"; import { inspect, type Message } from "effector/inspect"; import { someEvent } from "./app-code"; function logInspectMessage(m: Message) { const { name, value, kind } = m; return console.log(`[${kind}] ${name} ${value}`); } const myScope = fork(); inspect({ scope: myScope, fn: (m) => { logInspectMessage(m); }, }); someEvent(42); // ☝️ Нет логов! Это потому, что отслеживание было ограничено myScope allSettled(someEvent, { scope: myScope, params: 42 }); // [event] someEvent 42 // [on] 42 // [store] $count 1337 ``` ### Трассировка Добавление настройки `trace: true` позволяет просматривать предыдущие вычисления, которые привели к текущему. Это полезно для отладки конкретной причины возникновения некоторых событий. #### Пример ```ts import { fork, allSettled } from "effector"; import { inspect, type Message } from "effector/inspect"; import { someEvent, $count } from "./app-code"; function logInspectMessage(m: Message) { const { name, value, kind } = m; return console.log(`[${kind}] ${name} ${value}`); } const myScope = fork(); inspect({ scope: myScope, trace: true, // <- явная настройка fn: (m) => { if (m.kind === "store" && m.sid === $count.sid) { m.trace.forEach((tracedMessage) => { logInspectMessage(tracedMessage); // ☝️ здесь мы логируем трассировку обновления конкретного стора }); } }, }); allSettled(someEvent, { scope: myScope, params: 42 }); // [on] 42 // [event] someEvent 42 // ☝️ трассировки предоставляются в обратном порядке, так как мы смотрим назад во времени ``` ### Ошибки Effector не допускает исключений в чистых функциях. В таком случае вычисление ветви останавливается, и исключение логируется. Также в таком случае есть специальный тип сообщения: #### Пример ```ts inspect({ fn: (m) => { if (m.type === "error") { // сделать что-то с этим console.log(`${m.kind} ${m.name} computation has failed with ${m.error}`); } }, }); ``` ## Inspect Graph Позволяет отслеживать объявления юнитов, фабрик и регионов. ### Пример ```ts import { createStore } from "effector"; import { inspectGraph, type Declaration } from "effector/inspect"; function printDeclaration(d: Declaration) { console.log(`${d.kind} ${d.name}`); } inspectGraph({ fn: (d) => { printDeclaration(d); }, }); const $count = createStore(0); // выведет "store $count" в консоль ``` ### `withRegion` Метаданные, предоставленные через корневой узел региона, доступны при объявлении. #### Пример ```ts import { createNode, withRegion, createStore } from "effector"; import { inspectGraph, type Declaration } from "effector/inspect"; function createCustomSomething(config) { const $something = createStore(0); withRegion(createNode({ meta: { hello: "world" } }), () => { // какой-то код }); return $something; } inspectGraph({ fn: (d) => { if (d.type === "region") console.log(d.meta.hello); }, }); const $some = createCustomSomething({}); // выведет "world" ``` # is Объект с валидаторами юнитов ## Методы ### `is.store(value)` Проверяет, является ли переданное значение **Возвращает** boolean ```js import { is, createStore, createEvent, createEffect, createDomain } from "effector"; const store = createStore(null); const event = createEvent(); const fx = createEffect(); is.store(store); // => true is.store(event); // => false is.store(fx); // => false is.store(createDomain()); // => false is.store(fx.pending); // => true is.store(fx.done); // => false is.store(store.updates); // => false is.store(null); // => false ``` Запустить пример ### `is.event(value)` Проверяет, является ли переданное значение **Возвращает** boolean ```js import { is, createStore, createEvent, createEffect, createDomain } from "effector"; const store = createStore(null); const event = createEvent(); const fx = createEffect(); is.event(store); // => false is.event(event); // => true is.event(fx); // => false is.event(createDomain()); // => false is.event(fx.pending); // => false is.event(fx.done); // => true is.event(store.updates); // => true is.event(null); // => false ``` Запустить пример ### `is.effect(value)` Проверяет, является ли переданное значение **Возвращает** boolean ```js import { is, createStore, createEvent, createEffect, createDomain } from "effector"; const store = createStore(null); const event = createEvent(); const fx = createEffect(); is.effect(store); // => false is.effect(event); // => false is.effect(fx); // => true is.effect(createDomain()); // => false is.effect(null); // => false ``` Запустить пример ### `is.domain(value)` Проверяет, является ли переданное значение **Возвращает** boolean ```js import { is, createStore, createEvent, createEffect, createDomain } from "effector"; const store = createStore(null); const event = createEvent(); const fx = createEffect(); is.domain(store); // => false is.domain(event); // => false is.domain(fx); // => false is.domain(createDomain()); // => true is.domain(null); // => false ``` Запустить пример ### `is.scope(value)` > INFO: > > Добавлен в effector 22.0.0 Проверяет, является ли переданное значение **Возвращает** boolean ```js import { fork } from "effector"; const store = createStore(null); const event = createEvent(); const fx = createEffect(); const scope = fork(); is.scope(scope); // => true is.scope(store); // => false is.scope(event); // => false is.scope(fx); // => false is.scope(createDomain()); // => false is.scope(null); // => false ``` Запустить пример ### `is.unit(value)` Проверяет, является ли переданное значение юнитом: стором, эвентом, эффектом, доменом или скоупом **Возвращает** boolean ```js import { is, createStore, createEvent, createEffect, createDomain, fork } from "effector"; const store = createStore(null); const event = createEvent(); const fx = createEffect(); const scope = fork(); is.unit(scope); // => true is.unit(store); // => true is.unit(event); // => true is.unit(fx); // => true is.unit(createDomain()); // => true is.unit(fx.pending); // => true is.unit(fx.done); // => true is.unit(store.updates); // => true is.unit(null); // => false ``` Запустить пример ### `is.attached(value)` > INFO: > > Добавлен в effector 22.4.0 Проверяет, что переданный был создан с помощью метода . Если в качестве аргумента был передан не effect, возвращает `false`. **Возвращает** boolean ```js import { is, createStore, createEvent, createEffect, createDomain, attach } from "effector"; const $store = createStore(null); const event = createEvent(); const fx = createEffect(); const childFx = attach({ effect: fx, }); is.attached(childFx); // => true is.attached(fx); // => false is.attached($store); // => false is.attached(event); // => false is.attached(createDomain()); // => false is.attached(null); // => false ``` Запустить пример #### Пример использования Иногда нужно добавить отображение ошибок на эффекты, но только на те, которые были "локализованы" через `attach`. Если оставить `onCreateEffect` как есть, без проверок, то лог ошибки будет задублирован. ```js import { createDomain, attach, is } from "effector"; const logFailuresDomain = createDomain(); logFailuresDomain.onCreateEffect((effect) => { if (is.attached(effect)) { effect.fail.watch(({ params, error }) => { console.warn(`Effect "${effect.compositeName.fullName}" failed`, params, error); }); } }); const baseRequestFx = logFailuresDomain.createEffect((path) => { throw new Error(`path ${path}`); }); const loadDataFx = attach({ mapParams: () => "/data", effect: baseRequestFx, }); const loadListFx = attach({ mapParams: () => "/list", effect: baseRequestFx, }); loadDataFx(); loadListFx(); ``` Запустить пример # launch Низкоуровневый метод для запуска вычислений в юнитах. В основном используется разработчиками библиотек для тонкого контроля вычислений > INFO: > > Добавлен в effector 20.10.0 ## Методы ### Формула ```ts declare const $store: Store declare const event: Event declare const fx: Effect launch({target: $store, params: T}): void launch({target: event, params: T}): void launch({target: fx, params: T}): void ``` # merge Объединяет апдейты массива юнитов в новое событие, которое будет срабатывать при запуске любой из переданных сущностей > INFO: > > Добавлено в effector 20.0.0 ## Методы ### Формула ```ts declare const $store: Store ; // триггер declare const event: Event ; // триггер declare const fx: Effect ; // триггер const result: Event = merge(/*clock*/ [$store, event, fx]); ``` #### Аргументы * **`clock`**: Массив юнитов для объединения #### Возвращает : Новое событие > TIP: > > В случае передачи стора, итоговое событие будет срабатывать при обновлении этого стора ### Примеры ##### Пример 1 ```js import { createEvent, merge } from "effector"; const foo = createEvent(); const bar = createEvent(); const baz = merge([foo, bar]); baz.watch((v) => console.log("merged event triggered: ", v)); foo(1); // => merged event triggered: 1 bar(2); // => merged event triggered: 2 ``` Запустить пример ##### Пример 2 ```js import { createEvent, createStore, merge } from "effector"; const setFoo = createEvent(); const setBar = createEvent(); const $foo = createStore(0).on(setFoo, (_, v) => v); const $bar = createStore(100).on(setBar, (_, v) => v); const anyUpdated = merge([$foo, $bar]); anyUpdated.watch((v) => console.log(`state changed to: ${v}`)); setFoo(1); // => state changed to: 1 setBar(123); // => state changed to: 123 ``` Запустить пример ##### Пример 3 ```js import { createEvent, createStore, merge } from "effector"; const setFoo = createEvent(); const otherEvent = createEvent(); const $foo = createStore(0).on(setFoo, (_, v) => v); const merged = merge([$foo, otherEvent]); merged.watch((v) => console.log(`merged event payload: ${v}`)); setFoo(999); // => merged event payload: 999 otherEvent("bar"); // => merged event payload: bar ``` Запустить пример # effector/babel-plugin Поскольку Effector позволяет автоматизировать множество стандартных задач (например, задавание стабильных идентификаторов и предоставление отладочной информации для юнитов), существует встроенный плагин для Babel, который улучшает опыт разработчика при использовании библиотеки. ## Использование Пожалуйста, обратитесь к документации Babel plugin для примеров использования. # effector/compat ```ts import {} from "effector/compat"; ``` Библиотека предоставляет отдельный модуль с поддержкой совместимости до IE11 и Chrome 47 (браузер для устройств Smart TV). > WARNING Бандлер, а не транспилятор: > > Поскольку сторонние библиотеки могут импортировать `effector` напрямую, вам **не следует** использовать транспиляторы, такие как Babel, для замены `effector` на `effector/compat` в вашем коде, так как по умолчанию Babel не преобразует сторонний код. > > **Используйте бандлер**, так как он заменит `effector` на `effector/compat` во всех модулях, включая модули из сторонних библиотек. ### Необходимые полифиллы Вам нужно установить полифиллы для этих объектов: * `Promise` * `Object.assign` * `Array.prototype.flat` * `Map` * `Set` В большинстве случаев бандлер может автоматически добавить полифиллы. #### Vite ## Использование ### Ручная замена Вы можете использовать `effector/compat` вместо пакета `effector`, если вам нужно поддерживать старые браузеры. ```diff - import {createStore} from 'effector' + import {createStore} from 'effector/compat' ``` ### Автоматическая замена Однако вы можете настроить ваш бандлер для автоматической замены `effector` на `effector/compat` в вашем коде. #### WebpackПример конфигурации Vite
```js import { defineConfig } from "vite"; import legacy from "@vitejs/plugin-legacy"; export default defineConfig({ plugins: [ legacy({ polyfills: ["es.promise", "es.object.assign", "es.array.flat", "es.map", "es.set"], }), ], }); ```#### ViteПример конфигурации Webpack
```js module.exports = { resolve: { alias: { effector: "effector/compat", }, }, }; ```# effector/inspect ## `effector/inspect` Effector предоставляет специальные методы API, предназначенные для обработки задач отладки и мониторинга, не предоставляя слишком много доступа к внутренней логике вашего приложения — Inspect API. ### Почему отдельный модуль? Inspect API разработан как опциональный модуль. По задумке, любая функциональность, использующая Inspect API, может быть удалена из production-сборки без каких-либо побочных эффектов. Чтобы подчеркнуть это, Inspect API не включён в основной модуль. Вместо этого он доступен в отдельном модуле `effector/inspect`. ### Использование Пожалуйста, обратитесь к документации Inspect API для примеров использования. # restore ```ts import { restore } from "effector"; ``` ## Методы ### `restore(event, defaultState)` Создает из . Работает как сокращение для `createStore(defaultState).on(event, (_, payload) => payload)`. > WARNING Это не производный стор: > > `restore` создает новый стор. Это не производный стор. Это означает, что вы можете изменять его состояние через события и использовать его как `target` в sample. #### Формула ```ts restore(event: EventПример конфигурации Vite
```js import { defineConfig } from "vite"; export default defineConfig({ resolve: { alias: { effector: "effector/compat", }, }, }); ```, defaultState: T): StoreWritable ``` #### Аргументы 1. `event` 2. `defaultState` (*Payload*) #### Возвращает : Новый стор. #### Примеры ##### Базовый пример ```js import { createEvent, restore } from "effector"; const event = createEvent(); const $store = restore(event, "default"); $store.watch((state) => console.log("state: ", state)); // state: default event("foo"); // state: foo ``` Запустить пример ### `restore(effect, defaultState)` Создает из успешных результатов . Работает как сокращение для `createStore(defaultState).on(effect.done, (_, {result}) => result)`. #### Формула ```ts restore(effect: Effect , defaultState: Done): StoreWritable `. Также `defaultState` должен иметь тип `Done`. #### Примеры ##### Эффект ```js import { createEffect, restore } from "effector"; const fx = createEffect(() => "foo"); const $store = restore(fx, "default"); $store.watch((state) => console.log("state: ", state)); // => state: default await fx(); // => state: foo ``` Запустить пример ### `restore(shape)` Создает объект с сторами из объекта с значениями. #### Формула TBD #### Аргументы 1. `shape` (*State*) #### Возвращает : Новый стор. #### Примеры ##### Объект ```js import { restore } from "effector"; const { foo: $foo, bar: $bar } = restore({ foo: "foo", bar: 0, }); $foo.watch((foo) => { console.log("foo", foo); }); // => foo 'foo' $bar.watch((bar) => { console.log("bar", bar); }); // => bar 0 ``` Запустить пример # sample API [units]: /ru/explanation/glossary#common-unit [eventApi]: /ru/api/effector/Event [storeApi]: /ru/api/effector/Store [effectApi]: /ru/api/effector/Effect [purity]: /ru/explanation/glossary/#purity ## `sample` API ```ts import { sample } from "effector"; ``` Метод для связывания юнитов. Его главная задача - брать данные из одного места `source` и передавать их в другое место `target` при срабатывании определённого триггера `clock`. Типичный вариант использования – когда необходимо обработать какое-либо событие используя данные из стора. Вместо использования `store.getState()`, которое может вызвать несогласованность состояния, лучше использовать метод `sample`. > TIP как работать с sample: > > Узнайте как композировать юниты и работать с методом ### Алгоритм работы * При срабатывании `clock` прочитать значение из `source` * Если указан `filter`, и результат функции вернул `true` или стор со значением `true`, то продолжить * Если указан `fn`, то преобразовать данные * И передать данные в `target`. ### Особенности работы `sample` * Если `clock` не передан, `sample` будет срабатывать при каждом обновлении `source`. * Если `target` не передан, то `sample` создаст и вернёт новый производный юнит ### Возвращаемый юнит и значение Если `target` не передан, то он будет создан при вызове. Тип создаваемого юнита описан в данной таблице: | clock \ source | | | | | ----------------------------------- | --------------------------------- | --------------------------------- | ----------------------------------- | | | `Store` | `Event` | `Event` | | | `Event` | `Event` | `Event` | | | `Event` | `Event` | `Event` | Использование таблицы: 1. Выбираем тип источника `clock`, это столбец 2. Тип `source` – это строка 3. Устанавливаем соответствие между столбцом и строкой В случае, если `target` передан явно, то возвращаемым значением будет тот же самый `target`. Например: ```ts const event = createEvent(); const $store = createStore(); const $secondStore = createStore(); const $derivedStore = sample({ clock: $store, source: $secondStore, }); // Результатом будет производный стор, // так как `source` и `clock` являются сторами const derivedEvent = sample({ clock: event, source: $store, }); // Результатом будет производное событие, так как `clock` – событие ``` ### Полная форма * **Формула** ```ts sample({ clock?, // триггер source?, // источник данных filter?, // фильтр fn?, // функция-трансформатор target?, // целевой юнит batch?, // флаг батчинга name? // имя sample юнита }) ``` #### `clock` Аргумент `clock` является триггером, определяющий момент взятия данных из source.
Является опциональным. * **Тип** ```ts sample({ clock?: Unit| Unit [], }) ``` Может иметь сигнатуру: * [`Event `][eventApi] - срабатывает при вызове события * [`Store `][storeApi] - срабатывает при изменении стора * [`Effect `][effectApi] - срабатывает при вызове эффекта * `Unit []`- массив [юнитов][units] срабатывает при активации любого из них > INFO либо clock либо source: > > Хотя аргумент `clock` является опциональным, при использовании метода `sample` необходимо указать либо `clock`, либо source. ```ts const clicked = createEvent(); const $store = createStore(0); const fetchFx = createEffect(); // Event как clock sample({ source: $data, clock: clicked, }); // Store как clock sample({ source: $data, clock: $store, }); // Массив как clock sample({ source: $data, clock: [clicked, fetchFx.done], }); ``` *** #### `source` Является источником данных, откуда берутся данные при срабатывании `clock`. Если `clock` не указан, тогда `source` используется как `clock`.
Является опциональным. * **Тип** ```ts sample({ source?: Unit| Unit [] | {[key: string]: Unit }, }) ``` Может иметь сигнатуру: * [`Store `][storeApi] - данные берутся из текущего значения стора * [`Event `][eventApi] - возьмется последнее значение, с которым запускалось событие * [`Effect `][effectApi] - возьмется последнее значение, с которым запускался эффект * Объект с [юнитами][units] - для комбинирования нескольких источников * Массив с [юнитами][units] - для комбинирования нескольких источников > INFO либо source либо clock: > > Хотя аргумент `source` является опциональным, при использовании метода `sample` необходимо указать либо `source`, либо clock. *** #### `filter` Функция-предикат для фильтрации. Если возвращает `false` или стор со значением `false`, данные не будут переданы в `target`.
Является опциональным. * **Тип** ```ts sample({ filter?: Store| (source: Source, clock: Clock) => (boolean | Store ), }) ``` Может иметь сигнатуру: * [`Store `][storeApi] – стор с `boolean` значением, как производный так и базовый * Функция-предикат – функция возвращающая `boolean` значение ```ts const $isUserActive = createStore(false); sample({ clock: checkScore, source: $score, filter: (score) => score > 100, target: showWinnerFx, }); sample({ clock: action, source: $user, filter: $isUserActive, target: adminActionFx, }); ``` *** #### `fn` Функция для трансформации данных перед передачей в `target`. Функция [**должна быть чистой**][purity].
Является опциональным. * **Тип** ```ts sample({ fn?: (source: Source, clock: Clock) => Target }) ``` > INFO возвращаемый тип данных: > > Тип возвращаемых данных должен совпадать с типом данных в `target`. ```ts const $user = createStore({}); const saveUserFx = createEffect((user: User) => { // ... }); sample({ clock: updateProfile, source: $user, fn: (user, updates) => ({ ...user, ...updates }), target: saveUserFx, }); sample({ clock: submit, source: $form, fn: (form) => form.email, target: sendEmailFx, }); ``` *** #### `target` Целевой юнит, который получит данные и будет вызван.
Является опциональным. * **Тип** ```ts sample({ target?: Unit| Unit