Реактивность в effector

Реактивность — это фундаментальная концепция в Effector, которая позволяет автоматически обновлять данные при изменении их зависимостей. Вместо ручного управления обновлениями, вы описываете связи между различными частями вашего приложения, а Effector сам заботится об их синхронизации.

Что такое реактивность?

В основе реактивности лежит принцип автоматического распространения изменений. Когда меняется значение одного стора, все зависящие от него части приложения обновляются автоматически.

import { createStore, createEvent, combine } from "effector";

const firstWordChanged = createEvent<string>();

const $firstWord = createStore("Imperative");
const $secondWord = createStore("Programming");

const $fullSentence = combine($firstWord, $secondWord, (firstWord, secondWord) => {
  return `${firstWord} ${secondWord}`;
});

const $fullSentenceLength = $fullSentence.map((fullSentence) => fullSentence.length);

// Связываем их
$firstWord.on(firstWordChanged, (_, newWord) => newWord);

// Вызов события приведет к обновлению стора и всех подписчиков
firstWordChanged("Reactive");

В этом примере при изменении состояния хранилища $firstWord у нас автоматически изменится также $fullSentence и $fullSentenceLength, таким образом вам не нужно в ручную обновлять значения, этим займется effector.

Что делает Effector реактивным?

  1. Автоматическое распространение изменений. Когда значение стора меняется, Effector автоматически уведомляет все зависимые юниты:
import { createStore } from "effector";

const $users = createStore<User[]>([]);
const $userCount = $users.map((users) => users.length);
const $hasUsers = $users.map((users) => users.length > 0);

// $userCount и $hasUsers автоматически обновятся
// при любом изменении $users
  1. Декларативные связи. Вместо императивного описания что и когда должно произойти, мы декларативно описываем связи между данными:
import { sample, createStore, createEvent, createEffect } from "effector";

const formSubmitted = createEvent();
const $formData = createStore({ name: "", email: "" });
const submitToServerFx = createEffect(({ name, email }: { name: string; email: string }) => {
  // logic
});

sample({
  clock: formSubmitted,
  source: $formData,
  target: submitToServerFx,
});
  1. Предсказуемость обновлений. Обновления в Effector всегда происходят в определенном порядке, что делает поведение приложения предсказуемым:
import { createStore, createEvent, sample, createEffect } from "effector";

const $a = createStore(1);
const $b = createStore(2);
const updated = createEvent();

const updateFirstFx = createEffect(() => {
  // logic
});

const updateSecondFx = createEffect(() => {
  // logic
});

// Обновления будут происходить в порядке объявления
sample({
  clock: updated,
  source: $a,
  target: updateFirstFx,
});

sample({
  clock: updateFirstFx.done,
  source: $b,
  target: updateSecondFx,
});

Как работают связи между юнитами

Effector управляет зависимостями между юнитами (сторами, событиями и эффектами), обеспечивая правильный порядок обновлений. Когда одни юниты зависят от других, Effector гарантирует, что изменения распространяются предсказуемо:

import { createStore, createEvent, sample, createEffect } from "effector";

const fetchDataFx = createEffect(async () => {
  // вызов api
});

// Создаем юниты
const buttonClicked = createEvent();
const $isLoading = createStore(false);
const $error = createStore<Error | null>(null);

// Создаем зависимости
$isLoading
  .on(buttonClicked, () => true)
  // Сброс состояния при завершении загрузки
  .reset([fetchDataFx.done, fetchDataFx.fail]);

// При клике запускаем загрузку
sample({
  clock: buttonClicked,
  target: fetchDataFx,
});

buttonClicked();

Пример из реальной жизни

Рассмотрим пример поисковой строки с автоматическим обновлением результатов:

import { createStore, createEvent, createEffect, sample } from "effector";

// События и эффекты
const searchQueryChanged = createEvent<string>();

// 4
const searchFx = createEffect(async (query: string) => {
  const response = await fetch(`/api/search?q=${query}`);

  return response.json();
});

// Состояния
const $searchQuery = createStore("");
const $searchResults = createStore([]);
const $isSearching = searchFx.pending;

// Связи
$searchQuery.on(searchQueryChanged, (_, query) => query); // 2
// Обновляем результаты при успешном поиске
// 5
$searchResults.on(searchFx.doneData, (_, results) => results);

// Запускаем поиск при изменении запроса
// 3
sample({
  clock: searchQueryChanged,
  source: $searchQuery,
  target: searchFx,
});

searchQueryChanged("qwerty"); // 1
  1. Где-то в приложении вызвали searchQueryChanged.
  2. Обновляем стор $searchQuery.
  3. При помощи sample мы декларативно вызываем target (searchFx) с данными из source.
  4. Эффект searchFx выполняется.
  5. В случае успешного эффекта searchFx мы обновляем данные в сторе searchResults
Перевод поддерживается сообществом

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

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

Соавторы