Реактивное программирование основы
Reactive — модель, в которой изменение одной величины автоматически пересчитывает все зависящие от неё значения. В JS реализуется через getter/setter, Proxy или библиотеки типа RxJS.
Идея
Реактивное программирование в основном необходимо для того, чтобы мы моделировали при помощи реакций и вычислений какую-то предметную область — связывать свойства объектов, ячейки таблиц, структуры (деревья, графы, списки).
Главный пример: электронная таблица. Изменил A1 — пересчитались B1, C1 и все формулы, ссылающиеся на A1.
Реактивность через getter/setter
class Cone {
constructor(h, r1, r2) {
this.con = { h, r1, r2 };
this.calculate;
}
set h(value) { this.con.h = value; this.calculate; }
get volume { return this.con.volume; }
calculate { /* пересчитать всё */ }
}
В setter вызывается полный пересчёт. getter делегирует значения наружу. После каждого изменения переменной, мы вручную вместо того, чтобы посчитать её одну, вызываем метод calculate.
Lazy ≠ reactive
Можно было бы сами вычисления перенести на getter, но тогда бы это не было реактивным программированием.
| Lazy (getter) | считает только когда запрашивают |
| Reactive (setter) | меняем одно — сразу пересчитываются другие |
Реактивность на Proxy
const data = {};
const cell = new Proxy(data, {
get(obj, key) { return obj[key] ?? ''; },
set(obj, key, value) {
if (typeof value === 'function') {
obj[key] = { fn: value, value: value };
} else {
obj[key] = value;
}
return true; // ОБЯЗАТЕЛЬНО
}
});
Прокси — это примерно как getter и setter, только для всех свойств сразу. Универсальный getter/setter.
На выходе из Proxy set обязательно возвращать true, иначе будет TypeError.
Spreadsheet на Proxy
table.D5 = () => max(table.D2, table.D3, table.D4);
Функция кладётся в ячейку. В Proxy set вычисляем значение, храним и формулу, и значение. Чтение возвращает только value.
Хранение плоское — не матрица, а коллекция только заданных ячеек. A0 и AAA9999 — всего две ячейки, не миллион пустых.
Sandbox для упрощения формул
Можно так сделать, чтобы у нас spreadsheet лежал в sandbox, и там внутри были глобальные переменные. Формулы можно писать как max(d2, d3, d4) без префикса table.
Через vm.createScript и runInContext можно вычислять формулы с переменными ячеек как глобалами.
Граф зависимостей
Когда формула F5 читает D2, D3, D4 — Proxy get логирует эти обращения. Так можно построить граф зависимостей и при изменении D3 знать, какие формулы пересчитывать.
Пример с диапазоном
К сожалению, такого синтаксиса как D2:D4 мы в JavaScript не добьёмся, но можем добавить функцию range.
sum(...range('D2', 'D5')); // вместо sum(D2:D5)
Где применяется
- UI: Vue/Solid/Svelte реактивность (Proxy под капотом)
- MobX: автотрекинг зависимостей и пересчёт
- Spreadsheet-like приложения
- Системы автоматизации (датчики → правила → управляющие воздействия)
- Игры с пересчётом производных параметров
Связанные темы
Ресурсы
- Лекция: 7MH8-qQc-48