MVC и MVP в контексте Mithril

Mithril — это легковесный JavaScript-фреймворк для создания одностраничных приложений (SPA), отличающийся высокой производительностью и минимальным размером. Его архитектура позволяет гибко использовать популярные паттерны проектирования, такие как MVC (Model-View-Controller) и MVP (Model-View-Presenter).

Модель (Model)

Модель отвечает за управление состоянием приложения и работу с данными. В Mithril модели часто представляют собой простые объекты с методами для изменения состояния и выполнения асинхронных запросов.

Пример модели:

const TodoModel = {
    list: [],
    load() {
        return m.request({
            method: "GET",
            url: "/api/todos",
        })
        .then(result => {
            TodoModel.list = result;
        });
    },
    add(todo) {
        TodoModel.list.push(todo);
    },
    remove(id) {
        TodoModel.list = TodoModel.list.filter(t => t.id !== id);
    }
};

Ключевое в моделях Mithril — это реактивность через обновление состояния и последующий вызов m.redraw() или использование встроенной автоматической перерисовки после промисов m.request.

Представление (View)

Представление в Mithril реализуется через функции, возвращающие виртуальный DOM (vDOM). Эти функции полностью декларативны и описывают, как должны отображаться данные.

Пример простого представления:

const TodoView = {
    view(vnode) {
        return m("ul",
            vnode.attrs.todos.map(todo =>
                m("li", todo.text)
            )
        );
    }
};

Особенность Mithril — возможность передачи данных через attrs и использование их внутри vDOM-функций. Это делает представления чистыми и независимыми от бизнес-логики.

Контроллер (Controller)

В паттерне MVC контроллер управляет взаимодействием между моделью и представлением. В Mithril классический контроллер можно реализовать через объект или функцию, которая управляет состоянием и обработкой событий.

Пример контроллера для списка задач:

const TodoController = {
    init() {
        TodoModel.load();
    },
    addTodo(text) {
        TodoModel.add({ id: Date.now(), text });
    },
    removeTodo(id) {
        TodoModel.remove(id);
    }
};

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

Presenter в MVP

MVP отделяет представление от логики, предоставляя Presenter как посредника между View и Model. Presenter управляет состоянием, обрабатывает пользовательские действия и изменяет модель, не заставляя View содержать бизнес-логику.

Пример Presenter:

const TodoPresenter = () => {
    const addTodo = (text) => {
        TodoModel.add({ id: Date.now(), text });
    };
    const removeTodo = (id) => {
        TodoModel.remove(id);
    };
    return { addTodo, removeTodo, todos: TodoModel.list };
};

View в MVP становится максимально «тупым», отображая данные и вызывая методы Presenter по событиям.

Пример View с Presenter:

const TodoViewMVP = {
    view(vnode) {
        const { todos, addTodo, removeTodo } = vnode.attrs.presenter;
        return m("div", [
            m("input[type=text][placeholder=New task]", { oninput: e => vnode.state.newTodo = e.target.value }),
            m("button", { onclick: () => { addTodo(vnode.state.newTodo); vnode.state.newTodo = ""; } }, "Add"),
            m("ul", todos.map(todo =>
                m("li", [
                    todo.text,
                    m("button", { onclick: () => removeTodo(todo.id) }, "Remove")
                ])
            ))
        ]);
    }
};

Сравнение MVC и MVP в контексте Mithril

  • MVC

    • Контроллер управляет как моделью, так и представлением.
    • View может напрямую обращаться к модели через контроллер.
    • Подходит для небольших приложений или простых интерфейсов.
  • MVP

    • Presenter полностью изолирует логику от View.
    • View лишь отображает данные и передает действия пользователю.
    • Упрощает тестирование и поддержку больших приложений.

Рекомендации по применению

  1. Для небольших проектов или прототипов можно использовать MVC с минимальным контроллером.
  2. Для крупных SPA с большим количеством состояний и сложными взаимодействиями между компонентами предпочтителен MVP.
  3. В обоих подходах важно сохранять реактивность модели через m.redraw() или промисы m.request, чтобы интерфейс оставался синхронизированным с состоянием.

Использование Mithril совместно с паттернами MVC и MVP позволяет строить приложения, где бизнес-логика четко отделена от отображения, а компоненты остаются компактными и легко тестируемыми.