Инкапсуляция состояния

Инкапсуляция состояния является одной из ключевых концепций при построении приложений на Mithril.js. Фреймворк предоставляет легковесные, но мощные инструменты для управления состоянием компонентов, позволяя отделить данные от представления и обеспечивая предсказуемое поведение интерфейса.

Компоненты и их состояние

В Mithril.js компонент может быть представлен объектом с методами view и oninit или функцией, возвращающей виртуальный DOM. Основной способ инкапсуляции состояния — хранение его внутри компонента.

const Counter = {
    oninit(vnode) {
        vnode.state.count = 0;
    },
    view(vnode) {
        return m("div", [
            m("p", `Счётчик: ${vnode.state.count}`),
            m("button", { onclick: () => vnode.state.count++ }, "Увеличить")
        ]);
    }
};

Ключевой момент: объект vnode.state создаётся для каждого экземпляра компонента, что гарантирует изоляцию состояния между разными экземплярами. Это предотвращает нежелательные побочные эффекты при повторном использовании компонента.

Локальное состояние через замыкания

Для функциональных компонентов возможна инкапсуляция состояния через замыкания, без использования vnode.state. Такой подход удобен для небольших модулей и уменьшает зависимость от структуры vnode.

function Timer() {
    let seconds = 0;

    setInterval(() => {
        seconds++;
        m.redraw();
    }, 1000);

    return {
        view() {
            return m("div", `Прошло секунд: ${seconds}`);
        }
    };
}

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

Передача состояния через параметры

Компоненты могут получать состояние извне через объект attrs. Это позволяет разделять данные и логику, делая компонент повторно используемым и тестируемым.

const Message = {
    view(vnode) {
        return m("p", vnode.attrs.text);
    }
};

m.mount(document.body, {
    view() {
        return m(Message, { text: "Привет, Mithril!" });
    }
});

Важный момент: attrs являются входными параметрами и должны рассматриваться как неизменяемые внутри компонента. Изменение attrs внутри компонента нарушает предсказуемость и может привести к багам.

Глобальное состояние и его инкапсуляция

Для сложных приложений требуется управление общим состоянием. Mithril не накладывает строгих ограничений на глобальные объекты, однако правильная инкапсуляция достигается через модули или паттерн store, который эксплуатирует замыкания и публичные методы для доступа к данным.

const store = (() => {
    let todos = [];

    return {
        getTodos() {
            return todos;
        },
        addTodo(item) {
            todos.push(item);
            m.redraw();
        }
    };
})();

Компонент может использовать store без прямого доступа к внутреннему массиву:

const TodoList = {
    view() {
        return m("ul", store.getTodos().map(todo => m("li", todo)));
    }
};

Преимущество: глобальное состояние управляется централизованно, при этом внешние компоненты не могут напрямую изменять внутренние данные, что минимизирует ошибки и улучшает поддержку кода.

Реактивность и redraw

Mithril использует стратегию виртуального DOM с ручным управлением перерисовкой через m.redraw(). Инкапсуляция состояния требует понимания, когда и как инициировать перерисовку.

  • Локальное состояние: изменения через vnode.state автоматически отслеживаются при событиях DOM (например, onclick).
  • Глобальное состояние: изменения требуют вызова m.redraw(), если они происходят вне событий DOM.
setTimeout(() => {
    store.addTodo("Новая задача");
}, 2000);

Итоговые принципы инкапсуляции в Mithril

  1. Использование vnode.state для изолированного локального состояния.
  2. Замыкания для функциональных компонентов, обеспечивающие приватность данных.
  3. attrs как неизменяемый интерфейс для передачи данных в компонент.
  4. Модули/Stores для централизованного, но безопасного управления глобальным состоянием.
  5. Контроль redraw для синхронизации состояния и интерфейса.

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