combine API

combine API

import { combine } from "effector";

Метод combine позволяет получить состояние из каждого переданного стора и комбинировать их в одно значение, сохраняя его в новом производном сторе. Полученный стор будет обновляться каждый раз, когда обновляется любой из переданных сторов.

Алгоритм

  1. combine читает текущее состояние из всех переданных сторов.
  2. Если предоставлена функция трансформации fn, она вызывается со значениями из сторов.
  3. Результат сохраняется в новом производном сторе.
  4. Производный стор обновляется при каждом изменении любого из исходных сторов.

Особенности

  • Батчинг обновлений: Когда несколько сторов обновляются одновременно (за один тик), combine обрабатывает их все сразу, что приводит к единственному обновлению производного стора.
  • Смешивание сторов с примитивами: В combine можно передавать не только сторы, но и примитивы и объекты. Effector не будет отслеживать мутации этих примитивов и объектов - они рассматриваются как статические значения.
  • Чистые функции трансформации: Все функции трансформации, передаваемые в combine, должны быть чистыми.
  • Проверка строгого равенства: Если функция трансформации возвращает то же значение, что и предыдущее (сравнение через !==), производный стор не обновится.
  • Обработка ошибок: Если функция трансформации выбрасывает ошибку во время выполнения, приложение завершится сбоем. Это будет исправлено в effector v24.

Формы конфигурации

Форма
Описание
combine($a, fn)Трансформирует значение одного стора через fn.
combine(...$stores, fn?)Комбинирует несколько сторов/значений, опционально трансформируя через fn.
combine({ a: $a, b: $b }, fn?)Комбинирует сторы в объект-стор, опционально трансформируя через fn.
combine([$a, $b], fn?)Комбинирует сторы в массив-стор, опционально трансформируя через fn.

Конфигурации

combine($store, fn)

Создает новый производный стор путем трансформации значения одного стора. Это предпочтительный метод для создания производных сторов с одним источником. Является альтернативным способом для Store.map().

  • Формула
const $result = combine($source, (value) => {
// логика трансформации
return result;
});
  • Тип
export function combine<A, R>(
a: Store<A>,
fn: (a: A) => R,
config?: {
skipVoid?: boolean;
},
): Store<R>;
  • Особенности

    • Функция трансформации fn должна быть чистой.
    • Если fn возвращает то же значение, что и предыдущее (сравнение через !==), стор не обновится.
    • Если fn выбрасывает ошибку во время выполнения, приложение завершится сбоем (это будет исправлено в effector v24).
    • Опциональный параметр config: см. конфигурацию createStore для деталей о skipVoid.
  • Примеры

import { createStore, combine } from "effector";
const $name = createStore("John");
const $greeting = combine($name, (name) => `Hello, ${name}!`);
$greeting.watch((greeting) => console.log(greeting));
// => Hello, John!
import { createStore, combine } from "effector";
const $price = createStore(100);
const $priceWithTax = combine($price, (price) => price * 1.2);
$priceWithTax.watch((price) => console.log("Price with tax:", price));
// => Price with tax: 120
  • Возвращает

Новый производный стор.


combine(...$stores, fn?)

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

Без fn оборачивает все значения в массив. С fn трансформирует значения с помощью функции, где сторы передаются как отдельные аргументы.

  • Формула
// без функции трансформации
const $result = combine($a);
const $result = combine($a, $b, $c);
// с функцией трансформации
const $result = combine($a, (value) => {
// логика трансформации
return result;
});
const $result = combine($a, $b, $c, (a, b, c) => {
// логика трансформации
return result;
});
  • Тип
export function combine<T extends any[]>(
...stores: T
): Store<{ [K in keyof T]: T[K] extends Store<infer U> ? U : T[K] }>;
export function combine<T extends any[], R extends any>(
...stores: T,
fn: (...stores: [K in keyof T]: T[K] extends Store<infer U> ? U : T[K]) => R,
config?: { skipVoid?: boolean },
): Store<R>;
// также поддерживает любое количество сторов с опциональной fn в качестве последнего аргумента
  • Особенности

    • Принимает любое количество сторов, примитивов или объектов в качестве аргументов.
    • Можно смешивать сторы со значениями как примитивы или объекты.
    • Без fn возвращает массив-стор со значениями в том же порядке, что и аргументы.
    • Когда предоставлена fn:
      • Функция трансформации fn должна быть чистой.
      • Если fn возвращает то же значение, что и предыдущее (сравнение через !==), стор не обновится.
      • Если fn выбрасывает ошибку во время выполнения, приложение завершится сбоем (это будет исправлено в effector v24).
      • Опциональный параметр config: см. конфигурацию createStore для деталей о skipVoid.
  • Примеры

Без функции трансформации - создает массив-стор:

import { createStore, combine } from "effector";
const $firstName = createStore("John");
const $lastName = createStore("Doe");
const $age = createStore(30);
const $userData = combine($firstName, $lastName, $age);
$userData.watch((data) => console.log(data));
// => ["John", "Doe", 30]

С функцией трансформации - трансформирует комбинированные значения:

import { createStore, combine } from "effector";
const $firstName = createStore("John");
const $lastName = createStore("Doe");
const $fullName = combine($firstName, $lastName, (first, last) => {
return `${first} ${last}`;
});
$fullName.watch((name) => console.log(name));
// => "John Doe"

Комбинирование нескольких сторов с трансформацией:

import { createStore, combine } from "effector";
const $price = createStore(100);
const $quantity = createStore(2);
const $discount = createStore(0.1);
const $total = combine($price, $quantity, $discount, (price, qty, disc) => {
const subtotal = price * qty;
return subtotal - subtotal * disc;
});
$total.watch((total) => console.log(`Total: $${total}`));
// => Total: $180

Смешивание сторов с примитивами:

import { createStore, combine } from "effector";
const $userName = createStore("Alice");
const API_URL = "https://api.example.com";
const $userEndpoint = combine($userName, API_URL, (name, url) => {
return `${url}/users/${name}`;
});
$userEndpoint.watch((endpoint) => console.log(endpoint));
// => https://api.example.com/users/Alice
  • Возвращает

Новый производный стор.


combine({ a: $a, b: $b }, fn?)

Создает новый производный стор, который комбинирует объект сторов. Без fn создает объект-стор со значениями из переданных сторов. С fn трансформирует комбинированные значения с помощью функции.

  • Формула
// без функции трансформации
const $result = combine({
a: $a,
b: $b,
// ... больше сторов
});
// с функцией трансформации
const $result = combine({ a: $a, b: $b, c: $c }, ({ a, b, c }) => {
// логика трансформации
return result;
});
  • Тип
export function combine<State, R>(
shape: State,
fn?: (shape: { [K in keyof State]: State[K] extends Store<infer U> ? U : State[K] }) => R,
config?: {
skipVoid?: boolean;
},
): Store<R>;
  • Особенности

    • Обновления батчатся, когда несколько сторов изменяются одновременно.
    • Можно смешивать сторы со значениями как примитивы или объекты.
    • если предоставлена fn:
      • Функция трансформации fn должна быть чистой.
      • Если fn возвращает то же значение, что и предыдущее (сравнение через !==), стор не обновится.
      • Если fn выбрасывает ошибку во время выполнения, приложение завершится сбоем (это будет исправлено в effector v24).
      • Опциональный параметр config: см. конфигурацию createStore для деталей о skipVoid.
  • Примеры

Без функции трансформации - создает объект-стор:

import { createStore, combine } from "effector";
const $firstName = createStore("John");
const $lastName = createStore("Doe");
const $age = createStore(30);
const $user = combine({
firstName: $firstName,
lastName: $lastName,
age: $age,
});
$user.watch((user) => console.log(user));
// => { firstName: "John", lastName: "Doe", age: 30 }

С функцией трансформации - трансформирует значения объекта:

import { createStore, combine } from "effector";
const $firstName = createStore("John");
const $lastName = createStore("Doe");
const $age = createStore(30);
const $userSummary = combine(
{ firstName: $firstName, lastName: $lastName, age: $age },
({ firstName, lastName, age }) => {
return `${firstName} ${lastName}, ${age} years old`;
},
);
$userSummary.watch((summary) => console.log(summary));
// => "John Doe, 30 years old"

Практический пример - валидация формы:

import { createStore, combine } from "effector";
const $email = createStore("");
const $password = createStore("");
const $confirmPassword = createStore("");
const $formValidation = combine(
{ email: $email, password: $password, confirmPassword: $confirmPassword },
({ email, password, confirmPassword }) => {
const errors = [];
if (!email.includes("@")) {
errors.push("Invalid email");
}
if (password.length < 8) {
errors.push("Password must be at least 8 characters");
}
if (password !== confirmPassword) {
errors.push("Passwords don't match");
}
return {
isValid: errors.length === 0,
errors,
};
},
);

Смешивание сторов с примитивами и объектами:

import { createStore, combine } from "effector";
const $userId = createStore(123);
const $isActive = createStore(true);
const $userData = combine({
id: $userId,
isActive: $isActive,
role: "user",
permissions: ["read", "write"],
});
$userData.watch((data) => console.log(data));
// => { id: 123, isActive: true, role: "user", permissions: ["read", "write"] }
  • Возвращает

Новый производный стор.


combine([$a, $b], fn?)

Создает новый производный стор, который комбинирует массив сторов. Без fn создает массив-стор со значениями из переданных сторов в том же порядке. С fn трансформирует комбинированные значения с помощью функции.

  • Формула
// без функции трансформации
const $result = combine([$a, $b, $c]);
// с функцией трансформации
const $result = combine([$a, $b, $c], ([a, b, c]) => {
// логика трансформации
return result;
});
  • Тип
export function combine<State extends any[]>(
tuple: State,
): Store<{ [K in keyof State]: State[K] extends Store<infer U> ? U : State[K] }>;
export function combine<State extends any[], R>(
tuple: State,
fn: (tuple: { [K in keyof State]: State[K] extends Store<infer U> ? U : State[K] }) => R,
config?: { skipVoid?: boolean },
): Store<R>;
  • Особенности

    • Порядок массива совпадает с порядком переданных сторов.
    • Обновления батчатся, когда несколько сторов изменяются одновременно.
    • Можно смешивать сторы с не-сторовыми значениями, такими как примитивы или объекты.
    • Когда предоставлена fn:
      • Функция трансформации fn должна быть чистой.
      • Функция получает массив, в котором порядок совпадает с порядком входного массива.
      • Если fn возвращает то же значение, что и предыдущее (сравнение через !==), стор не обновится.
      • Если fn выбрасывает ошибку во время выполнения, приложение завершится сбоем (это будет исправлено в effector v24).
      • Опциональный параметр config: см. конфигурацию createStore для деталей о skipVoid.
  • Примеры

Без функции трансформации - создает массив-стор:

import { createStore, combine } from "effector";
const $x = createStore(10);
const $y = createStore(20);
const $z = createStore(30);
const $coordinates = combine([$x, $y, $z]);
$coordinates.watch((coords) => console.log(coords));
// => [10, 20, 30]

С функцией трансформации - трансформирует значения массива:

import { createStore, combine } from "effector";
const $x = createStore(3);
const $y = createStore(4);
const $distance = combine([$x, $y], ([x, y]) => {
return Math.sqrt(x * x + y * y);
});
$distance.watch((dist) => console.log(`Distance: ${dist}`));
// => Distance: 5

Практический пример - вычисление итогов из массива:

import { createStore, combine } from "effector";
const $itemPrice1 = createStore(10);
const $itemPrice2 = createStore(25);
const $itemPrice3 = createStore(15);
const $cartTotal = combine([$itemPrice1, $itemPrice2, $itemPrice3], (prices) => {
return prices.reduce((sum, price) => sum + price, 0);
});
$cartTotal.watch((total) => console.log(`Total: $${total}`));
// => Total: $50

Массив с разными типами:

import { createStore, combine } from "effector";
const $userName = createStore("Alice");
const $score = createStore(100);
const $isActive = createStore(true);
const $playerInfo = combine([$userName, $score, $isActive], ([name, score, active]) => {
return `Player ${name}: ${score} points (${active ? "online" : "offline"})`;
});
$playerInfo.watch((info) => console.log(info));
// => Player Alice: 100 points (online)

Смешивание сторов с примитивами в массиве:

import { createStore, combine } from "effector";
const $currentPage = createStore(1);
const MAX_PAGES = 10;
const $pagination = combine([$currentPage, MAX_PAGES], ([current, max]) => {
return {
current,
max,
hasNext: current < max,
hasPrev: current > 1,
};
});
$pagination.watch((pagination) => console.log(pagination));
// => { current: 1, max: 10, hasNext: true, hasPrev: false }
  • Возвращает

Новый производный стор.

Перевод поддерживается сообществом

Документация на английском языке - самая актуальная, поскольку её пишет и обновляет команда effector. Перевод документации на другие языки осуществляется сообществом по мере наличия сил и желания.

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

Соавторы