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
Такой подход обеспечивает чистое разделение данных и вычислений, а также упрощает управление состоянием приложения.
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
на каждом изменении, чтобы уменьшить количество промежуточных
вычислений.
Streams обеспечивают чистое и предсказуемое управление состоянием, делают код декларативным и легко тестируемым. В сочетании с компонентами Mithril это создаёт лёгкую, но мощную реактивную архитектуру для фронтенд-приложений.