Мемоизация — это сохранение результатов вычислений для повторного
использования при тех же входных данных. В Mithril она особенно важна
из-за частых перерисовок: любой вызов m.redraw() приводит к
повторному выполнению функций view, а значит — к повторным
вычислениям. При сложных представлениях и производительных расчётах это
становится узким местом.
Мемоизация в Mithril не встроена как отдельный механизм, но легко реализуется за счёт функционального стиля, замыканий и жизненного цикла компонентов.
Mithril использует виртуальный DOM и диффинг, но функции
view вызываются полностью при каждом обновлении.
Даже если DOM не меняется, вычисления внутри view
выполняются заново.
Критичные места:
vnode;attrs и
state.Поэтому кэширование результатов вычислений должно находиться
вне тела view или быть привязано к
жизненному циклу компонента.
Функциональные компоненты позволяют хранить кэш в замыкании:
function createSum() {
let lastArgs = null;
let lastResult = null;
return function(a, b) {
if (lastArgs && lastArgs[0] === a && lastArgs[1] === b) {
return lastResult;
}
lastArgs = [a, b];
lastResult = a + b;
return lastResult;
};
}
const memoizedSum = createSum();
Такой подход применим для:
Компонентное состояние (vnode.state) — естественное
место для хранения кэша:
const ExpensiveComponent = {
oninit(vnode) {
vnode.state.cache = {
input: null,
result: null
};
},
view(vnode) {
const data = vnode.attrs.data;
const cache = vnode.state.cache;
if (cache.input !== data) {
cache.input = data;
cache.result = heavyComputation(data);
}
return m("div", cache.result);
}
};
Преимущества:
attrs.onbeforeupdate для контроля обновленийХук onbeforeupdate позволяет полностью пропустить
обновление компонента:
const StaticView = {
onbeforeupdate(vnode, old) {
return vnode.attrs.value !== old.attrs.value;
},
view(vnode) {
return m("span", vnode.attrs.value);
}
};
Если возвращается false, view не
вызывается. Это форма структурной мемоизации, полезная
для компонентов, зависящих от ограниченного набора параметров.
WeakMapДля вычислений, зависящих от объектов, эффективно использовать
WeakMap:
const cache = new WeakMap();
function compute(model) {
if (cache.has(model)) {
return cache.get(model);
}
const result = heavyComputation(model);
cache.set(model, result);
return result;
}
Преимущества:
Подходит для:
key)При работе со списками важно использовать key:
items.map(item =>
m(ItemView, { key: item.id, item })
);
key обеспечивает:
vnode.state между перерисовками;Без key кэш в vnode.state будет
теряться.
Частый шаблон — вычисление производных данных из состояния:
function createSelector(fn) {
let lastInput;
let lastOutput;
return function(input) {
if (input === lastInput) {
return lastOutput;
}
lastInput = input;
lastOutput = fn(input);
return lastOutput;
};
}
const selectVisibleItems = createSelector(items =>
items.filter(i => i.visible)
);
Использование в view:
view(vnode) {
const visible = selectVisibleItems(vnode.attrs.items);
return m("ul", visible.map(...));
}
Этот подход:
m.request уже возвращает промисы, но логика кэширования
может быть вынесена выше:
const requestCache = new Map();
function fetchUser(id) {
if (!requestCache.has(id)) {
requestCache.set(id, m.request({
method: "GET",
url: `/api/users/${id}`
}));
}
return requestCache.get(id);
}
Особенности:
oninit.Любая мемоизация требует стратегии инвалидции:
attrs;Типичные способы:
onupdate;Пример:
onupdate(vnode) {
if (vnode.attrs.version !== vnode.state.version) {
vnode.state.cache = null;
vnode.state.version = vnode.attrs.version;
}
}
Эффективная мемоизация в Mithril опирается на следующие принципы:
vnode.state;Мемоизация не заменяет правильную архитектуру, но усиливает её, позволяя сохранять производительность даже при сложных и насыщенных интерфейсах.