Перейти к основному содержимому

Effect

Effect (эффект) это контейнер для сайд-эффектов, возможно асинхронных. В комплекте имеет ряд заранее созданных эвентов и сторов, облегчающих стандартные действия. Является юнитом

Эффекты можно вызывать как обычные функции (императивный вызов) а также подключать их и их свойства в различные методы api включая sample, guard и split (декларативное подключение). При императивном вызове принимают максимум один аргумент и всегда возвращают промис, отражающий ход выполнения сайд-эффекта

Структура

  • Методы

    • map: создает производное событие на основе данных эффекта
    • prepend: создаёт событие-триггер для преобразования данных перед запуском эффекта
    • watch: вызывает дополнительную функцию с сайд-эффектами при каждом срабатывании эффекта
    • use: определяет имплементацию эффекта: функцию, которая будет вызвана при срабатывании
  • Свойства

    • doneData: событие, которое срабатывает с результатом выполнения эффекта
    • failData: событие, которое срабатывает с ошибкой, возникшей при выполнении эффекта
    • pending: стор, который показывает, что эффект находится в процессе выполнения
    • done: событие, которое срабатывает с результатом выполнения эффекта и аргументом, переданным при вызове
    • fail: событие, которое срабатывает с ошибкой, возникшей при выполнении эффекта и аргументом, переданным при вызове
    • finally: событие, которое срабатывает при завершении эффекта с подробной информацией об аргументах, результатах и статусе выполнения
    • inFlight: стор, который показывает число запущенных эффектов, которые находятся в процессе выполнения
    • shortName: имя эффекта
    • sid: стабильный идентификатор эффекта

Примеры

Общий пример использования

const getUserFx = createEffect(async params => {
const req = await fetch(`https://example.com/get-user/${params.id}`)
return req.json()
})

// подписка на начало вызова эффекта
getUserFx.watch(params => {
console.log('эффект вызван с аргументом', params)
})

// подписка на успешное завершение вызова эффекта
getUserFx.done.watch(({result, params}) => {
console.log('вызвов с аргументом', params)
console.log('завершён со значением', result)
})

// подписка на исключение, возникшее в процессе работы эффекта
getUserFx.fail.watch(({error, params}) => {
console.log('вызов с аргументом', params)
console.log('завершён с ошибкой', error)
})

// вызов эффекта с параметрами
getUserFx({id: 1})
// => эффект вызван с аргументом {id: 1}
// => вызвов с аргументом {id: 1}
// завершён с ошибкой TypeError: Failed to fetch

// имплементацию эффекта можно заменять
getUserFx.use(() => 'test result')

// императивное получение результатов вызова
const data = await getUserFx({id: 2})
// => эффект вызван с аргументом {id: 2}
// => вызвов с аргументом {id: 2}
// завершён со значением test result

Запустить пример


Методы

map

Создает производное событие на основе данных эффекта

Формула

declare const fxA: Effect<T, any>

const eventB = fxA.map(/*fn*/(data: T) => S)
-> Event<S>

При вызове fxA, функция-обработчик fn будет вызвана с поступившими данными, после чего производное событие eventB будет вызвано с результатом вычислений


fxA -> fn -> eventB

Аргументы

  1. fn: (data: T) => S

    Функция-обработчик, которая будет вычислять данные для передачи в производное событие eventB на основе данных из fxA. Должна быть чистой

    Аргументы

    • data: Данные с которыми сработал эффект fxA

    Возвращает

    Данные для передачи в производное событие eventB

Возвращает

Новое, производное событие

Примеры

Пример использования map
import {createEffect} from 'effector'

const updateUserFx = createEffect(({name, role}) => {})
const userNameUpdate = updateUserFx.map(({name}) => name)
const userRoleUpdate = updateUserFx.map(({role}) => role.toUpperCase())

userNameUpdate.watch(name => {
console.log(`Началось изменение имени пользователя на ${name}`)
})
userRoleUpdate.watch(role => {
console.log(`Началось изменение роли пользователя на ${role}`)
})

await updateUserFx({name: 'john', role: 'admin'})
// => Началось изменение имени пользователя на john
// => Началось изменение роли пользователя на ADMIN

Запустить пример

prepend

Создаёт событие-триггер для преобразования данных перед запуском эффекта. По сравнению с map, работает в обратном направлении

Формула

declare const fx: Effect<S, any>

const trigger = fx.prepend(/*fn*/(data: T) => S)
-> Event<T>

При вызове события trigger, функция-обработчик fn будет вызвана с поступившими данными, после чего fx будет вызван с результатом вычислений


trigger -> fn -> fx

Аргументы

  1. fn: (data: T) => S

    Функция-обработчик, которая будет вычислять данные для передачи в fx на основе данных события trigger. Должна быть чистой

    Аргументы

    • data: Данные с которыми сработало событие trigger

    Возвращает

    Данные для передачи в fx

Возвращает

Новое событие

watch

Вызывает дополнительную функцию с сайд-эффектами при каждом срабатывании эффекта

note

По мере усложнения логики проекта оптимальнее заменить на комбинацию дополнительного эффекта и сэмпла

Формула

declare const fx: Effect<T, any>

fx.watch(/*watcher*/ (data: T) => any)
-> Subscription

Аргументы

  1. watcher: (data: T) => any

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

Возвращает

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

Примеры

Пример использования watch
import {createEffect} from 'effector'

const fx = createEffect(params => params)

fx.watch(params => {
console.log('эффект вызван с аргументом', params)
})

await fx(10)
// => эффект вызван с аргументом 10

Запустить пример

use

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

Если на момент вызова эффект уже имел имплементацию, то она будет заменена на новую

статья от автора
note

Нужно предоставить имплементацию либо через use, либо через createEffect, иначе при вызове эффекта возникнет ошибка "no handler used in %effect name%"

Формула

declare const fx: Effect<T, S>

fx.use(/*handler*/(params: T) => S | Promise<S>)

Аргументы

  1. handler: (params: T) => S | Promise<S>

    Функция-имплементация эффекта. Может быть как синхронной, так и асинхронной

    Аргументы

    1. params: Данные, с которыми был вызван эффект

    Возвращает

    Результат выполнения эффекта в виде значения, либо в виде промиса со значением

Возвращает

Текущий эффект

note

Если значение имплементации известно сразу, то оптимальнее использовать createEffect(handler)

createEffect().use(handler) это антипаттерн, который ухудшает вывод типов

Примеры

Пример использования use
import {createEffect} from 'effector'

const fetchUserReposFx = createEffect()

// ....

fetchUserReposFx.use(async ({name}) => {
console.log('fetchUserReposFx вызван для github пользователя', name)

const url = `https://api.github.com/users/${name}/repos`
const req = await fetch(url)
return req.json()
})

await fetchUserReposFx({name: 'zerobias'})
// => fetchUserReposFx вызван для github пользователя zerobias

Запустить пример


Свойства

doneData

Событие, которое срабатывает с результатом выполнения эффекта

Формула

declare const fx: Effect<any, D>

fx.doneData
-> Event<D>
Вызов вручную запрещён

Это свойство управляется самим эффектом

note

Добавлено в effector 20.12.0

Примеры

Пример использования doneData
import {createEffect} from 'effector'

const fx = createEffect(value => value + 1)

fx.doneData.watch(result => {
console.log(`Эффект успешно выполнился, вернув ${result}`)
})

await fx(2)
// => Эффект успешно выполнился, вернув 3

Запустить пример

failData

Событие, которое срабатывает с ошибкой, возникшей при выполнении эффекта

Формула

declare const fx: Effect<any, any, E>

fx.failData
-> Event<E>
Вызов вручную запрещён

Это свойство управляется самим эффектом

note

Добавлено в effector 20.12.0

Примеры

Пример использования failData
import {createEffect} from 'effector'

const fx = createEffect(async value => {
throw Error(value - 1)
})

fx.failData.watch(error => {
console.log(`Вызов завершился с ошибкой ${error.message}`)
})

fx(2)
// => Вызов завершился с ошибкой 1

Запустить пример

pending

Стор, который показывает, что эффект находится в процессе выполнения

Формула

declare const fx: Effect<any, any>

fx.pending
-> Store<boolean>

Это свойство избавляет от необходимости писать подобный код:

import {createEffect, createStore} from 'effector'

const requestFx = createEffect()

const $isRequestPending = createStore(false)
.on(requestFx, () => true)
.on(requestFx.done, () => false)
.on(requestFx.fail, () => false)
Изменение значения вручную запрещено

Это свойство управляется самим эффектом

Примеры

Отображение индикатора загрузки с react
import React from 'react'
import ReactDOM from 'react-dom'
import {createEffect} from 'effector'
import {useStore} from 'effector-react'

const fetchApiFx = createEffect(async ms => {
await new Promise(resolve => setTimeout(resolve, ms))
})

fetchApiFx.pending.watch(console.log)
// => false

const App = () => {
const loading = useStore(fetchApiFx.pending)
return <div>{loading ? 'Загрузка...' : 'Загрузка завершена'}</div>
}

ReactDOM.render(<App />, document.getElementById('root'))

fetchApiFx(1000)
// => true
// => false

Запустить пример

done

Событие, которое срабатывает с результатом выполнения эффекта и аргументом, переданным при вызове

Формула

declare const fx: Effect<P, D>

fx.done
-> Event<{params: P; result: D}>
Вызов вручную запрещён

Это свойство управляется самим эффектом

Примеры

Пример использования done
import {createEffect} from 'effector'

const fx = createEffect(value => value + 1)

fx.done.watch(({params, result}) => {
console.log('Вызов с аргументом', params, 'завершён со значением', result)
})

await fx(2)
// => Вызов с аргументом 2 завершён со значением 3

Запустить пример

fail

Событие, которое срабатывает с ошибкой, возникшей при выполнении эффекта и аргументом, переданным при вызове

Формула

declare const fx: Effect<P, any, E>

fx.fail
-> Event<{params: P; error: E}>
Вызов вручную запрещён

Это свойство управляется самим эффектом

Примеры

Пример использования fail
import {createEffect} from 'effector'

const fx = createEffect(async value => {
throw Error(value - 1)
})

fx.fail.watch(({params, error}) => {
console.log(
'Вызов с аргументом',
params,
'завершился с ошибкой',
error.message,
)
})

fx(2)
// => Вызов с аргументом 2 завершился с ошибкой 1

Запустить пример

finally

Событие, которое срабатывает при завершении эффекта с подробной информацией об аргументах, результатах и статусе выполнения

Формула

declare const fx: Effect<P, D, E>

fx.finally
-> Event<
| {status: 'done'; params: P; result: D}
| {status: 'fail'; params: P; error: E}
>
Вызов вручную запрещён

Это свойство управляется самим эффектом

note

Добавлено в effector 20.0.0

Примеры

Пример использования finally
import {createEffect} from 'effector'

const fetchApiFx = createEffect(async ({time, ok}) => {
await new Promise(resolve => setTimeout(resolve, time))
if (ok) return `${time} ms`
throw Error(`${time} ms`)
})

fetchApiFx.finally.watch(value => {
switch (value.status) {
case 'done':
console.log(
'Вызов с аргументом',
value.params,
'завершён со значением',
value.result,
)
break
case 'fail':
console.log(
'Вызов с аргументом',
value.params,
'завершён с ошибкой',
value.error.message,
)
break
}
})

await fetchApiFx({time: 100, ok: true})
// => Вызов с аргументом {time: 100, ok: true}
// завершён со значением 100 ms

fetchApiFx({time: 100, ok: false})
// => Вызов с аргументом {time: 100, ok: false}
// завершён с ошибкой 100 ms

Запустить пример

inFlight

Стор, который показывает число запущенных эффектов, которые находятся в процессе выполнения. Используется для ограничения числа одновременных запросов

Формула

declare const fx: Effect<any, any>

fx.inFlight
-> Store<number>

Это свойство избавляет от необходимости писать подобный код:

import {createEffect, createStore} from 'effector'

const requestFx = createEffect()

const $requestsInFlight = createStore(0)
.on(requestFx, n => n + 1)
.on(requestFx.done, n => n - 1)
.on(requestFx.fail, n => n - 1)
Изменение значения вручную запрещено

Это свойство управляется самим эффектом

note

Добавлено в effector 20.11.0

Примеры

Пример использования inFlight
import {createEffect} from 'effector'

const fx = createEffect(async () => {
await new Promise(resolve => setTimeout(resolve, 500))
})

fx.inFlight.watch(amount => {
console.log('выполняется запросов:', amount)
})
// => выполняется запросов: 0

const req1 = fx()
// => выполняется запросов: 1

const req2 = fx()
// => выполняется запросов: 2

await Promise.all([req1, req2])

// => выполняется запросов: 1
// => выполняется запросов: 0

Запустить пример

shortName

Имя эффекта. Задаётся либо явно, через поле name в createEffect, либо автоматически через babel plugin. Используется для обработки сущностей программно, например при использовании хуков домена

Формула

declare const fx: Effect<any, any>

fx.shortName
-> string

sid

Стабильный идентификатор эффекта. Задаётся автоматически через babel-plugin

Формула

declare const fx: Effect<any, any>

fx.sid
-> string | null

Дополнительные методы

use.getCurrent

Метод для получения текущей имплементации эффекта. Используется для тестирования

Если у эффекта ещё не была установлена имплементация, то будет возвращена функция по умолчанию, при срабатывании она выбрасывает ошибку

Формула

declare const fx: Effect<P, D>

fx.use.getCurrent()
-> (params: P) => D

Возвращает

Функцию-имплементацию эффекта, которая была установлена через createEffect или с помощью метода use

Примеры

Пример использования getCurrent
const handlerA = () => 'A'
const handlerB = () => 'B'

const fx = createEffect(handlerA)

console.log(fx.use.getCurrent() === handlerA)
// => true

fx.use(handlerB)
console.log(fx.use.getCurrent() === handlerB)
// => true

Запустить пример