Mithril использует компактную и предсказуемую реализацию виртуального
DOM, ориентированную на минимизацию накладных расходов. В отличие от
более тяжёлых фреймворков, Mithril не хранит избыточные метаданные и не
строит сложные структуры для диффинга. Виртуальные узлы
(vnode) — это простые JavaScript-объекты, создаваемые
функцией m().
Каждый цикл перерисовки состоит из трёх этапов:
Оптимизация рендеринга в Mithril в первую очередь сводится к контролю над этими этапами.
Mithril предоставляет набор хуков жизненного цикла, позволяющих точно управлять моментами создания, обновления и удаления DOM-узлов.
Ключевые хуки для оптимизации:
Пример предотвращения лишнего обновления:
const Component = {
onbeforeupdate(vnode, old) {
return vnode.attrs.value !== old.attrs.value
},
view(vnode) {
return m("div", vnode.attrs.value)
}
}
Возврат false из onbeforeupdate полностью
блокирует диффинг поддерева, что особенно эффективно для тяжёлых
компонентов.
По умолчанию Mithril использует позиционный диффинг. При работе со списками это может приводить к лишним операциям с DOM. Указание ключей переводит диффинг в режим сопоставления по идентификаторам.
Пример неоптимального списка:
items.map(item => m("li", item.name))
Оптимизированный вариант:
items.map(item => m("li", { key: item.id }, item.name))
Ключи позволяют:
Особенно критично использовать ключи при сортировке, фильтрации и динамических обновлениях массивов.
Функции view вызываются при каждом рендере. Любые
вычисления внутри них должны быть либо минимальными, либо
закэшированными.
Распространённая ошибка — вычисление производных данных прямо в
view:
view() {
const filtered = items.filter(...)
return m("ul", filtered.map(...))
}
Оптимизация через мемоизацию:
let cached = null
let lastItems = null
function getFiltered(items) {
if (items !== lastItems) {
lastItems = items
cached = items.filter(...)
}
return cached
}
Вызов getFiltered(items) в view снижает
нагрузку при частых перерисовках без изменения входных данных.
Чем меньше поддерево, тем дешевле его диффинг. Mithril эффективно работает с большим количеством мелких компонентов.
Плохо масштабируемая структура:
view() {
return m("div", [
m("header", ...),
m("main", heavyContent()),
m("footer", ...)
])
}
Оптимизированная структура:
const Main = {
view() {
return heavyContent()
}
}
view() {
return m("div", [
m("header", ...),
m(Main),
m("footer", ...)
])
}
При таком разделении Mithril может изолировать обновления и не
пересчитывать всё дерево при изменениях, не затрагивающих
Main.
m.redrawMithril автоматически вызывает m.redraw() после
большинства асинхронных операций (events, promises). Это удобно, но не
всегда оптимально.
Отключение авто-перерисовки:
m.request({
method: "GET",
url: "/api/data",
background: true
})
Ручной контроль:
m.redraw()
Это позволяет:
Mithril не навязывает иммутабельность, но диффинг выигрывает от неё.
Сравнение по ссылке (===) работает быстрее и надёжнее, чем
глубокий анализ объектов.
Рекомендуемый подход:
map, filter,
slice вместо push, splice;Пример:
state.items = [...state.items, newItem]
Такой стиль упрощает оптимизации в onbeforeupdate и
повышает предсказуемость рендеринга.
Прямые манипуляции с DOM внутри oncreate и
onupdate допустимы, но должны быть строго ограничены. Любое
вмешательство в DOM, не отражённое в виртуальном дереве, увеличивает
риск рассинхронизации.
Допустимые сценарии:
Недопустимо:
Чем меньше Mithril «не знает» о состоянии DOM, тем эффективнее работает диффинг.
Каждое изменение атрибутов — потенциальная операция обновления DOM. Следует избегать создания новых объектов атрибутов при каждом рендере, если значения не меняются.
Неоптимально:
m("button", { onclick: () => doSomething() }, "OK")
Оптимально:
function onClick() {
doSomething()
}
m("button", { onclick: onClick }, "OK")
Переиспользование функций снижает количество сравнений и обновлений обработчиков.
Оптимизация в Mithril строится вокруг двух идей:
m.redraw;Фреймворк предоставляет низкоуровневые, но прозрачные инструменты, позволяющие точно управлять производительностью без скрытой магии и непредсказуемых оптимизаций.