Streams и реактивные потоки данных

Mithril предоставляет лёгкий и эффективный механизм управления реактивными данными через streams — функции, которые представляют собой значения, способные уведомлять подписчиков о своих изменениях. Этот подход позволяет реализовать реактивное программирование, где изменения состояния автоматически отражаются в интерфейсе без явного пересоздания компонентов.

Основы Mithril Streams

Streams в Mithril создаются с помощью функции m.stream(). При создании потока можно задать начальное значение:

const count = m.stream(0);

Значение потока можно получить вызовом функции без аргументов, а изменить — передав новое значение:

console.log(count()); // 0
count(1);
console.log(count()); // 1

Streams ведут себя как функции с двойным назначением: чтение и запись значения осуществляется единым вызовом. Это позволяет легко интегрировать потоки в вычисляемые выражения и связывать их с DOM через Mithril view.

Комбинирование потоков

Для построения более сложной логики можно комбинировать несколько потоков через функцию map. Она позволяет реагировать на изменения исходного потока и вычислять новое значение:

const a = m.stream(2);
const b = m.stream(3);
const sum = m.stream.combine((aVal, bVal) => aVal + bVal, [a, b]);

console.log(sum()); // 5
a(5);
console.log(sum()); // 8

m.stream.combine принимает два аргумента: функцию вычисления и массив потоков, от которых зависит результат. Каждый раз при изменении любого из потоков вычисляется новое значение.

Потоки и асинхронные операции

Mithril Streams поддерживают асинхронные потоки данных, что удобно при работе с API. Поток может быть промисом, который автоматически обновляет значение при разрешении:

const dataStream = m.stream();
m.request({ method: "GET", url: "/api/data" }).then(dataStream);

dataStream.map(data => {
    console.log("Получены данные:", data);
});

В этом примере поток dataStream будет обновлён, как только m.request вернёт результат, и все подписчики map будут уведомлены.

Цепочки потоков

Streams можно комбинировать в цепочки для построения реактивных зависимостей. Использование map возвращает новый поток, что позволяет создавать сложные вычисления:

const base = m.stream(10);
const double = base.map(x => x * 2);
const square = double.map(x => x * x);

console.log(square()); // 400
base(5);
console.log(square()); // 100

Такой подход обеспечивает чистое разделение данных и вычислений, а также упрощает управление состоянием приложения.

Потоки в компонентах Mithril

Streams хорошо интегрируются с компонентами Mithril, позволяя автоматически обновлять представление при изменении состояния:

const Counter = {
    oninit(vnode) {
        vnode.state.count = m.stream(0);
    },
    view(vnode) {
        return m("div", [
            m("button", { onclick: () => vnode.state.count(vnode.state.count() - 1) }, "-"),
            m("span", vnode.state.count()),
            m("button", { onclick: () => vnode.state.count(vnode.state.count() + 1) }, "+")
        ]);
    }
};

Каждое изменение vnode.state.count автоматически инициирует перерисовку компонента, обеспечивая реактивное обновление интерфейса без явного вызова m.redraw().

Производительность и оптимизация

Streams в Mithril имеют низкую нагрузку на систему, так как не требуют сложной подписной модели с деревьями зависимостей. Комбинации потоков вычисляются только при необходимости, а пересчёт значений минимален. Для больших приложений рекомендуется использовать комбинированные потоки вместо вызова map на каждом изменении, чтобы уменьшить количество промежуточных вычислений.

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

  • Реактивные формы: значения полей формы могут быть потоками, а кнопки и валидации — зависимыми вычислениями.
  • Асинхронные данные: загрузка с API или WebSocket легко интегрируется с потоками.
  • Комплексные состояния: сложные зависимости между параметрами UI можно выразить через цепочки потоков, избегая ручного контроля перерисовки.

Streams обеспечивают чистое и предсказуемое управление состоянием, делают код декларативным и легко тестируемым. В сочетании с компонентами Mithril это создаёт лёгкую, но мощную реактивную архитектуру для фронтенд-приложений.