sample
Метод для связывания юнитов связью вида "при срабатывании clock
прочитать значение из source
и передать в target
"
Типичный вариант использования - когда необходимо обработать какое-либо событие используя данные из стора. Вместо использования store.getState()
, которое может вызвать несогласованность состояния, лучше использовать метод sample
Формула
sample({ source?, clock?, filter?, fn?, target?}): target
При срабатывании clock
прочитать значение из source
и передать в target
- Если
clock
не передан, sample будет срабатывать при каждом обновленииsource
. - Если
filter
не передан, продолжить выполнение как есть. Еслиfilter
есть и возвращаетfalse
или его значениеStore<false>
, то отменить выполнение, а иначе продолжить - Если передан
fn
, то при срабатывании передать значения изsource
иclock
в эту функцию, а вtarget
передать результат вычисления - Если
target
не передан, то sample создаст и вернёт новый юнит
Иллюстрация принципа работы
Тип создаваемого target
Если target
не передан, то он будет создан при вызове. Тип создаваемого юнита описан в данной таблице:
clock\source | Store | Event | Effect |
---|---|---|---|
Store | Store | Event | Event |
Event | Event | Event | Event |
Effect | Event | Event | Event |
Использование таблицы:
- Выбираем тип источника
source
, это столбец - Тип
clock
- это строка - Устанавливаем соответствие между столбцом и строкой
Например:
const $store = sample({clock: $store, source: $store})
// Результатом будет стор, так как source и clock являются сторами
const event = sample({clock: event, source: $store})
// Результатом будет эвент, так как clock - не стор
sample({clock?, source?, fn?, target?, greedy?})
Основная запись метода
Аргументы
params
(Object): Объект конфигурации
clock?
: Юнит или массив юнитовРазновидности:
- событие или эффект: срабатывание этого события/эффекта будет запускать
target
- стор: обновление этого стора будет запускать
target
- массив юнитов: срабатывание любого из юнитов будет запускать
target
. Сокращение для вызова merge - поле отсутствует:
source
будет использоваться в качествеclock
- событие или эффект: срабатывание этого события/эффекта будет запускать
source?
: Юнит или массив/объект со сторамиРазновидности:
- событие или эффект: при срабатывании
clock
будет взято последнее значение с которым запускался этот юнит (перед этим он должен будет запуститься хотя бы раз) - стор: при срабатывании
clock
будет взято текущее значение этого стора - массив или объект со сторами: при срабатывании
clock
будут взяты текущие значения из заданных сторов, объединенных в объект или массив. Сокращение для вызова combine - поле отсутствует:
clock
будет использоваться в качествеsource
- событие или эффект: при срабатывании
target?
: Юнит или массив юнитовРазновидности:
- событие или эффект: при срабатывании
clock
будет вызван данный юнит - стор: при срабатывании
clock
состояние юнита будет обновлено - массив юнитов: при срабатывании
clock
будут запущены все юниты - поле отсутствует: новый юнит будет создан и возвращен в результате вызова sample. Его тип зависит от типов
clock
иsource
- событие или эффект: при срабатывании
fn?
:(sourceData, clockData) => result
Функция-обработчик, которая будет преобразовывать данные из
source
иclock
перед отправлением вtarget
, должна быть чистой. В случае отсутствия этого поля, данные изsource
будут передаваться вtarget
как естьgreedy?
:boolean
Модификатор, определяющий, будет ли
target
ожидать окончательного значенияclock
прежде чем запуститься самому. Приgreedy: false
target
будет срабатывать только раз после каждой серии идущих подряд обновлений, а приgreedy: true
,target
сработает по разу при каждом триггереclock
. Иными словами, эта опция отключает стабилизацию апдейтовclock
и вынуждает обрабатывать все промежуточные значения. Батчинг обновлений повышает общую эффективность работы системы, поэтому по умолчанию greedy установлен вfalse
Поддержка массивов юнитов в target
добавлена в effector 21.8.0
Возвращает
(Event | Store) - Юнит, который будет срабатывать при срабатывании clock
, если target
не передан.
Тип возвращаемого юнита зависит от типов clock
и source
Пример
const $userName = createStore('john')
const signIn = createEffect((params) => {
console.log(params)
})
const submitForm = createEvent()
sample({
clock: submitForm /* 1 */,
source: $userName /* 2 */,
fn: (name, password) => ({name, password}) /* 3 */,
target: signIn /* 4 */,
})
submitForm(12345678)
// 1. при вызове submitForm с аргументом 12345678
// 2. прочитать значение из стора $userName ('john')
// 3. преобразовать значение из submitForm (1) и $userName (2)
// 4. и передать результат вычислений в эффект signIn
sample(source, clock, fn?): Unit
Альтернативная запись метода, всегда имеет неявный target
Аргументы
source
: ЮнитРазновидности:
- событие или эффект: при срабатывании
clock
будет взято последнее значение с которым запускался этот юнит (перед этим он должен будет запуститься хотя бы раз) - стор: при срабатывании
clock
будет взято текущее значение этого стора
- событие или эффект: при срабатывании
clock
: ЮнитРазновидности:
- событие или эффект: срабатывание этого события/эффекта будет запускать
target
- стор: обновление этого стора будет запускать
target
- поле отсутствует:
source
будет использоваться в качествеclock
- событие или эффект: срабатывание этого события/эффекта будет запускать
fn?
:(sourceData, clockData) => result
Функция-обработчик, которая будет преобразовывать данные из
source
иclock
перед отправлением вtarget
, должна быть чистой. В случае отсутствия этого поля, данные изsource
будут передаваться вtarget
как есть. Поскольку этот обработчик призван организовывать поток данных, следует избегать объявления в нём сайд-эффектов. Правильнее будет поместить их в эффекты или в методwatch
возвращаемого юнита
Возвращает
(Event | Store) - Юнит, который будет срабатывать при срабатывании clock
, если target
не передан.
Тип возвращаемого юнита зависит от типов clock
и source
.
Пример
const $userName = createStore('john')
const signIn = createEffect((params) => {
console.log(params)
})
const submitForm = createEvent()
const sampleUnit = sample(
$userName /* 2 */,
submitForm /* 1 */,
(name, password) => ({name, password}) /* 3 */,
)
forward({
from: sampleUnit,
to: signIn /* 4 */,
})
submitForm(12345678)
// 1. при вызове submitForm с аргументом 12345678
// 2. прочитать значение из стора $userName ('john')
// 3. преобразовать значение из submitForm (1) и $userName (2)
// 4. и передать результат вычислений в эффект signIn
sample({name?})
Добавлено в effector 20.4.0
Любой юнит в эффекторе может иметь имя, поле name
в sample позволяет указать имя создаваемому target
import {createStore, sample} from 'effector'
const foo = createStore(null)
const sampled = sample({
source: foo,
name: 'sampled foo',
})
console.log(sampled.shortName) // 'sampled foo'
Объекты и массивы в source
Объект со сторами
Добавлено в effector 20.8.0
sample может быть вызван с объектом сторов в source
:
import {createStore, createEvent, sample} from 'effector'
const trigger = createEvent()
const a = createStore('A')
const b = createStore(1)
// target имеет тип `Event<{ a: string, b: number }>`
const target = sample({
clock: trigger,
source: {a, b},
})
target.watch((obj) => {
console.log('sampled object', obj)
})
trigger()
// => sampled object {a: 'A', b: 1}
Массив сторов
Добавлено в effector 20.8.0
sample может быть вызван с массивом сторов в source
:
import {createStore, createEvent, sample} from 'effector'
const trigger = createEvent()
const a = createStore('A')
const b = createStore(1)
// target имеет тип `Event<[string, number]>`
const target = sample({
clock: trigger,
source: [a, b],
})
target.watch((obj) => {
console.log('sampled array', obj)
})
// Можно деструктурировать аргументы, чтобы задать явные имена
target.watch(([a, b]) => {
console.log('explicit names', a, b)
})
trigger()
// => sampled array ["A", 1]
// => explicit names "A" 1
Массивы юнитов в clock
Добавлено в effector 21.2.0
Передача массивов юнитов в clock
работает как вызов merge
import {createStore, createEvent, createEffect, sample, merge} from 'effector'
const showNotification = createEvent<string>()
const trigger = createEvent()
const fx = createEffect()
const store = createStore('')
// массив юнитов в clock
sample({
clock: [trigger, fx.doneData],
source: store,
target: showNotification,
})
// объединённый юнит в clock
sample({
clock: merge([trigger, fx.doneData]),
source: store,
target: showNotification,
})
Пример с filter
effector Halley 22.2.0
Новый вариант использования sample
работает так же, но с одним дополнительным методом filter
. Когда filter
возвращает true
продолжить выполнение, иначе отменить. Взглянем на пример ниже.
Вася хочет отправить Пете деньги. Вася - отправитель, а Петя - получатель. Чтобы отправить деньги, отправитель должен знать адрес получателя, кроме того транзакция должна быть подписана. Пример показывает как работает sample
с filter
. Основные моменты, которые необходимо учесть:
- Убедиться, что баланс положительный и больше чем отправляемая сумма.
- Наличие адреса получателя
- Подписанная транзакция
- Убедиться, что баланс отправителя изменился
import {createStore, createEvent, createEffect, sample} from 'effector'
const sign = createEvent()
const sentMoney = createEvent()
const $recipientAddress = createStore('a23x3xd')
const $balance = createStore(20000)
const $isSigned = createStore(false)
const transactionFx = createEffect(
({amountToSend, recipientAddress}) =>
new Promise((res) =>
setTimeout(res, 3000, {
amount: amountToSend,
recipientAddress,
}),
),
)
$isSigned.on(sign, () => true).reset(transactionFx)
$balance.on(transactionFx.doneData, (balance, {amount}) => balance - amount)
sample({
source: {
recipientAddress: $recipientAddress,
isSigned: $isSigned,
balance: $balance,
},
clock: sentMoney,
filter: ({isSigned, balance}, amountToSend) =>
isSigned && balance > amountToSend,
fn({recipientAddress}, amountToSend) {
return {recipientAddress, amountToSend}
},
target: transactionFx,
})
$balance.watch((balance) => console.log('balance: ', balance))
$isSigned.watch((isSigned) => console.log('is signed: ', isSigned))
sign()
sentMoney(1000)