Локальное состояние компонентов

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

Структура компонента

Компонент в Mithril обычно представлен объектом с методами view и опционально oninit, oncreate, onupdate и другими жизненными циклами. Для хранения локального состояния чаще всего используется объект самого компонента:

const Counter = {
    count: 0,
    view: function() {
        return m("button", {
            onclick: () => this.count++,
        }, `Счётчик: ${this.count}`);
    }
};

Здесь count является локальным состоянием. При каждом клике на кнопку оно увеличивается. Однако, чтобы изменения корректно отображались, нужно использовать перерисовку компонентов, так как Mithril не отслеживает изменения автоматически.

const Counter = {
    count: 0,
    view: function() {
        return m("button", {
            onclick: () => {
                this.count++;
                m.redraw();
            },
        }, `Счётчик: ${this.count}`);
    }
};

Использование m.redraw() обеспечивает обновление DOM после изменения состояния.

Инициализация состояния через oninit

Часто состояние компонента зависит от начальных данных или требует инициализации:

const TodoItem = {
    oninit(vnode) {
        this.completed = vnode.attrs.completed || false;
    },
    view() {
        return m("div", [
            m("input[type=checkbox]", {
                checked: this.completed,
                onclick: () => {
                    this.completed = !this.completed;
                    m.redraw();
                }
            }),
            m("span", "Задача")
        ]);
    }
};

Метод oninit вызывается один раз при создании экземпляра компонента. Через параметр vnode можно передавать начальные данные и свойства, что делает локальное состояние гибким.

Использование функций для состояния

Для более сложных компонентов удобно использовать функции-конструкторы, возвращающие объект состояния:

function CounterComponent() {
    let count = 0;

    return {
        view() {
            return m("button", {
                onclick: () => {
                    count++;
                    m.redraw();
                }
            }, `Счётчик: ${count}`);
        }
    };
}

Такой подход предотвращает нежелательное разделение состояния между разными экземплярами компонента, поскольку каждый вызов функции создаёт новый независимый объект состояния.

Локальные методы и вычисляемые свойства

Состояние можно расширять локальными методами или вычисляемыми свойствами:

const Temperature = {
    celsius: 25,
    get fahrenheit() {
        return this.celsius * 9/5 + 32;
    },
    increase() {
        this.celsius++;
        m.redraw();
    },
    view() {
        return m("div", [
            m("p", `Температура: ${this.celsius}°C / ${this.fahrenheit}°F`),
            m("button", {onclick: () => this.increase()}, "Увеличить")
        ]);
    }
};

Свойство fahrenheit автоматически пересчитывается при изменении celsius, что позволяет держать логику внутри компонента.

Состояние и дочерние компоненты

Локальное состояние компонентов не зависит от родительского компонента, если оно не передано через атрибуты:

const Parent = {
    view() {
        return m("div", [
            m(Child, {initial: 5}),
            m(Child, {initial: 10})
        ]);
    }
};

const Child = {
    oninit(vnode) {
        this.value = vnode.attrs.initial;
    },
    view() {
        return m("div", `Значение: ${this.value}`);
    }
};

Каждый экземпляр Child имеет своё собственное состояние value, и изменения в одном не влияют на другой.

Рекомендации по организации состояния

  • Минимизировать глобальные зависимости. Локальное состояние должно быть максимально автономным.
  • Использовать oninit для инициализации. Это упрощает управление начальными данными и настройку компонентов.
  • Избегать мутаций переданных атрибутов. Для передачи данных лучше использовать копии или методы, чтобы сохранить предсказуемость состояния.
  • Сохранять чистоту компонентов. Локальное состояние должно отвечать за внутренние данные, не затрагивая глобальное приложение напрямую.

Взаимодействие состояния и жизненного цикла

Mithril предоставляет несколько хуков жизненного цикла, которые удобно использовать вместе с локальным состоянием:

  • oninit(vnode) — инициализация состояния и подготовка данных.
  • oncreate(vnode) — доступ к DOM после первой отрисовки, например для настройки внешних библиотек.
  • onupdate(vnode) — реакция на обновление состояния и перерисовку.
  • onremove(vnode) — очистка ресурсов при удалении компонента.

Сочетание этих хуков с локальным состоянием позволяет создавать изолированные, управляемые и повторно используемые компоненты без необходимости обращаться к глобальному хранилищу.

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