Перейти к содержанию
Mercury
Русский
Дизайн книга
Меню
Русский

Reflect.1. Связываем представление

После привязки представление остаётся компонентом. Хранилища дают значения, а действия — обработчики. Поле имени и счётчик позволяют увидеть оба направления связи.

Reflect 4.1.1 · Интерактивное упражнение

Открыть упражнение на всю страницу

Как устроена связь

Представлению не обязательно знать модель. В примере CounterView получает число и обработчик; его можно отдельно вывести с тестовыми свойствами. reflect задаёт значения по умолчанию из модели и возвращает новый компонент. Объявляйте модель и связанные компоненты вне рендера, чтобы при каждом обновлении не создавать новые узлы и конструкторы.

import {createElement as h} from "@fiberfx/hyperscript";
import {event, store} from "@fiberfx/hypergraph";
import {reflect, createReflect, fromTag} from "@fiberfx/reflect";

const nameChanged = event<string>();
const incremented = event<void>();
const $name = store("Ada").on(nameChanged, (_, name) => name);
const $count = store(0).on(incremented, count => count + 1);

export const Name = reflect({
  view: fromTag("input"),
  bind: {
    "aria-label": "Name",
    value: $name,
    onInput: (e: Event) =>
      nameChanged((e.currentTarget as HTMLInputElement).value),
  },
});
const CounterView = (p: {count: number; onIncrement: () => void}) =>
  h("button", {type: "button", onClick: p.onIncrement}, String(p.count));
const counter = createReflect(CounterView);
export const Counter = counter({count: $count, onIncrement: () => incremented()});

fromTag("input") превращает HTML-тег в представление и сохраняет известные типы его свойств. createReflect(CounterView) фиксирует представление: одну фабрику можно использовать для нескольких привязок. По смыслу это повторные вызовы reflect с тем же view.

Обработчик нативного поля получает событие, поэтому адаптер извлекает currentTarget.value. У Mercury Checkbox обработчик onChange получает boolean. Reflect не преобразует эти аргументы. Перед заменой контрола прочитайте главу поля и формы. Компоненты нужно выводить внутри Hypergraph Provider приложения; его роль разобрана в главе 3.

Измените пример

Откройте эксперимент с привязкой. Замените черновик имени и нажмите Применить имя, затем дважды увеличьте счётчик. Снова измените черновик, не применяя его. Поле и применённое имя показывают разные значения модели. В эксперименте Mercury Input связан через createReflect, а число выведено через fromTag("output"); короткий пример выше показывает те же операторы с нативным полем и представлением кнопки.

В варианте с кодом выведите h(Counter, {count: 99}). Теперь число для отображения задаёт вызывающая сторона, а обработчик увеличения остаётся связанным с моделью. Это переопределение свойства, а не запись в $count.

Проследите оба направления

Ввод должен менять черновик и поле. Применение непустого имени обновляет отдельное значение применённого имени. Кнопка «Прибавить один» меняет хранилище счётчика и его вывод. Представление получает текущее значение хранилища, а не объект Store.

В варианте count: 99 кнопка продолжит показывать 99, хотя обработчик меняет модель. Уберите переопределение — появится последнее значение из хранилища. Переданный извне обработчик так же заменяет связанный обработчик: Reflect не вызывает оба.

Нарушьте контракт привязки

Сначала очистите черновик и нажмите Применить имя. Должно появиться сообщение об ошибке, а последнее применённое имя должно сохраниться. Отказ обеспечивает фильтр sample в модели, а не Reflect.

Отдельная ошибка типов — путать строку с событием DOM. Проверка типов должна отклонить такую привязку:

reflect({view: fromTag("input"), bind: {
  onInput: nameChanged, // event<string> не принимает событие поля ввода
}});

Используйте явный адаптер из примера. Для действия без данных пишите () => incremented(), чтобы не передавать ему событие нажатия. Reflect связывает интерфейс с моделью, но не проверяет данные во время выполнения. Обход проверки типов не делает несовместимый обработчик правильным.

Сбросьте и повторите

Сбросьте эксперимент к начальным значениям. Введите другое имя и один раз увеличьте счётчик. Сброс упражнения восстанавливает его исходное состояние; он не отменяет произвольные побочные эффекты приложения.

В собственной тестовой сцене восстановите значения модели либо создайте новую принадлежащую ей область состояния и размонтируйте старые представления. Не создавайте новый связанный компонент внутри рендера ради очистки поля.

Примените к форме поиска

Свяжите поисковое поле через value: $query и адаптер, передающий строку в queryChanged. Проверку запроса и эффекты поиска оставьте в модели. Один раз выведите обычное представление поля с фиксированными свойствами и убедитесь, что обработчик получает ожидаемый аргумент.

Перед переходом дальше объясните обе границы: событие DOM превращается в данные действия, а значение хранилища — в свойство представления. Следующая глава добавляет вычисляемое свойство без создания узлов графа при рендере.

Назад · Курс · Далее