Skip to main content

Behaviors — обзор

Behaviors — реактивная логика формы поверх сигналов модели: вычисляемые поля, условная доступность полей, синхронизация значений, реакции на изменения и правила узлов схемы. Это отдельный слой — он отвечает за «как данные реагируют», тогда как валидация отвечает за «правильны ли данные».

Два способа писать behaviors​

Behaviors работают на сигналах модели. Есть две поверхности API — декларативная и императивная.

1. Декларативный DSL — @reformer/core/behaviors​

Рекомендуемый путь. Операторы (compute, enableWhen, onChange, …) вызываются внутри defineFormBehavior(({ model, form, schema, flow }) => { … }) и сами регистрируют свои отписки — автор не видит ни массива cleanup'ов, ни ручного управления жизненным циклом. Готовое поведение подключается к сборке полем behavior — форма владеет его жизненным циклом.

import { createForm } from '@reformer/core';
import { defineFormBehavior, compute, enableWhen } from '@reformer/core/behaviors';

type OrderForm = {
price: number;
quantity: number;
total: number;
discount: number | null;
};

const orderBehavior = defineFormBehavior<OrderForm>(({ model }) => {
// Вычисляемое поле — зависимости отслеживаются по прочитанным сигналам
compute(model.$.total, () => model.price * model.quantity);

// Условная доступность — условие читает model.* реактивно
enableWhen(model.$.discount, () => model.total > 500, { resetOnDisable: true });
});

// schema — дерево разметки (см. «Быстрый старт»)
const order = createForm<OrderForm>({
initial: { price: 100, quantity: 1, total: 0, discount: null },
schema: orderSchema,
behavior: orderBehavior,
});

Область поведения: model — модель, form — ноды формы, schema — узлы схемы своей области (schema.node(selector)), flow — поток шагов сборки (без flow в конфиге — undefined).

2. Императивные примитивы — @reformer/core/model​

Операции над значениями доступны отдельными функциями из @reformer/core/model: computeFrom, copyFrom, watchField, transformValue, resetWhen, syncFields, revalidateWhen. Они принимают сигналы (model.$.field), возвращают cleanup и вызываются императивно — например, в useEffect. Это escape-hatch, когда не нужно целое поведение. Примитива для compute нет — его роль играет computeFrom; для onChange — watchField. Операции над нодами (enableWhen, disableWhen) есть только в DSL.

import { useEffect } from 'react';
import { copyFrom, watchField } from '@reformer/core/model';

useEffect(() => {
const cleanups = [
copyFrom(model.$.email, model.$.emailAdditional, { when: () => model.sameEmail === true }),
watchField(model.$.country, () => {
model.city = '';
}),
];
return () => cleanups.forEach((cleanup) => cleanup());
}, []);
Что выбрать

DSL из @reformer/core/behaviors — по умолчанию: одно поведение, автоматические отписки, подключение одним createForm({ behavior }). Примитивы из @reformer/core/model — когда нужна точечная реакция без полного поведения (или вне React-дерева).

Операторы DSL​

ОператорНазначение
computeПроизводное значение, зависимости — по чтению
computeFromПроизводное значение с явными зависимостями
copyFromКопирование значения (скаляр или группа) по условию
syncFieldsДвусторонняя синхронизация двух полей
transformValueИдемпотентная трансформация значения на месте
enableWhenУсловное включение поля / полей / группы
disableWhenУсловное выключение (инверсия enableWhen)
resetWhenСброс значения по условию
onChangeРеакция на изменение (async, debounce, AbortSignal)
revalidateWhenПерепроверка правил при изменении зависимостей
applyПрименить поведение к полю-группе (переиспользование)
applyEachПрименить поведение к каждой строке массива
exclusiveFlagВзаимное исключение булева флага среди строк массива
aggregateIntoАгрегатная запись в строки массива

Операторы над узлами схемы записывают правило, а исполняет его рендерер (FormRenderer):

ОператорНазначение
hideWhenСкрывать узел, пока условие истинно
onComponentEvent(node, event, handler)Обработчик события компонента узла (onSubmit, …)
renderEffect(schema, fn)Реактивный эффект, живущий, пока форма отрисована
onInit / onMount / onUnmountЖизненный цикл узла

Узел берётся из схемы своей области: schema.node('mortgage') — по selector узла. Там, где форму рисуют руками (JSX без рендерера), правила узлов не действуют — видимость и обработчики остаются в разметке.

Как читать поля в behaviors

Внутри compute / условий when / enableWhen читай поля как model.field — подписка на сигнал происходит автоматически. Не читай model.get() в реактивном контексте: это нереактивный снимок, зависимость не отследится. Цели и источники операторов — сигналы model.$.field, а не строковые пути.

Как это работает​

  1. Объяви операторы внутри defineFormBehavior(({ model, form, schema, flow }) => { … }).
  2. Передай результат в сборку: createForm({ model, schema, behavior }).
  3. Сборка запускает поведение после построения нод: операторы подписываются на сигналы модели и срабатывают при изменениях, а их отписки собираются автоматически и вызываются при уничтожении формы.
// Когда price или quantity меняется → total пересчитывается
compute(model.$.total, () => model.price * model.quantity);

Behavior vs Валидация​

АспектВалидацияBehavior
ЗадачаПроверить корректностьРеагировать на изменения
РезультатЗамечания (issues нод)Побочные эффекты / производные значения
Запускbundle.validate(), bundle.submission.submit(), «Далее», стратегияРеактивно, автоматически
Где живётvalidation и шаги flow сборки (defineValidation)behavior сборки (defineFormBehavior)

Дальше​