Про Marionette.js
В сети сейчас можно найти много статей про модные ныне javascript фреймворки. Такие как Angular, React, Ember , Polymer воплощающие в себе достаточно интересные идеи и подходы + использующие относительно новые подходы при работе с веб страницей (прим. shadow-dom) и позволяющие делать восхитительные вещи, не без костылей конечно, но это уже другой разговор. Но есть еще один достаточно крупный фреймворк и это Marionette.js некоторые «продвинутые» фанаты мейнстримного фронтенда для себя похоронили этот фреймворк вместе с backbone. Но проект успешно продолжает развиваться и представляет из себя очень хороший фундамент для создания ваших веб приложений. И так будет еще очень долго, пока HTML5 и новые версии фундаментально не придумают что-то иное как работа с DOM. Так что за жизнь marionette можно не переживать.
За что я люблю marionette?
Все началось с backbone, это по сути не фреймворк, а библиотека реализующая некоторые абстрактные понятия, такие как Model, View, Collection а также Router и Sync, но это уже второстепенные возможности. Каждое из этих понятий вы конечно же можете extend’ить как вам захочется и понравится. Можно легко реализовать классический MVC, можно MVVM, MVP. если хочется можно и publisher subsciber реализовать. Это и есть фундаментальная свобода для разработки и масштабирования архитектуры под любые нужды клиента. Но такой подход далеко не всем понравится в силу того, что придется писать много шаблонного кода. Объяснять view как ей делать render, расписывать примитивные связи абстракций типа CollectionView ModelView и т.п. это еще одна слабая сторона backbone, это то, что биндинг данных в view лежит на разработчике очень плотно и заставляет писать некоторый «шаблонный код». Более популярные js фреймворки предоставляют разработчикам более высокие слои абстракции, что позволяет им делать за «5 минут» что-то типичное, но как только надо, вот прям надо бизнесу отойти от текущего бизнес процесса, или срочно внести что-то новое, вам придется побороться с этими самыми правилами наложенными на вас фреймворком. Вернемся к Marionette, это уже backbone фреймворк, как раз предостовляющий более конкретные объекты с которыми можно оперировать на более высоком уровне, которые в свою очередь унаследованы от абстракций backbone.
Рассмотрим поближе
Первое, что предлагает нам marionette это конечно само понятие Application в котором инициализация и старт приложения это разные понятия, также поддерживается событийная модель. Давайте попробуем написать наше первое приложение.
// Создаем приложение
var App = new Mn.Application();
// При старте приложения стартуем и Backbone.history
App.on('start', function() {
Backbone.history.start();
});
Пример из доки. Давайте немного усложним сами. И представим как могло бы выглядеть приложение немного побольше простого примера.
//Настало время расширить само понятие Application в Marionette
var App = Mn.Application.extend({
initialize: function initialize() {
//При инициализации попутно инциаилизируем нужные нам модели для работы приложения
this.User = new User(); //bb model
this.Router = new Router(); //mn router
this.Notices = new Notices(); //app notices marionette abstract view
this.Options = new Options(); //bb model
},
regions: {
layout: '#app'
},
//В место App.on... пользуемся приставкой onEvent
onStart: function onStart() {
Backbone.history.start();
//Изначально покажем лоадер, а там роутер уже решит какую View отобразить в главном layout нашего App
this.layout.show(this.loader);
}
});
//Инитим App
var AppInstance = new App();
//Стартуем App
AppInstance.start();
Я буду писать в ES5 стиле т.к. статья в первую очередь расчитана на начинающих в фронтенд разработке и не знакомых хорошо с ES6 синтаксисом. Но для продвинутого пользователя, могу сказать, что можно в целом писать и в таком виде
class App extends Mn.Appctaction {
counstructor() {
//have fun
}
}
Примеры с ES6 + Marionette можно посмотреть на github.
Если в целом окинуть взглядом документацию Marionette, можно заметить, что большинство абстракций это абстракции как раз для View. В marionette действительно создан замечательный механизм для работы с самого разного рода Views и следует уделить особое внимание работе с архитектурой views приложения. Люди начинающие программировать на Marionette часто путаются, когда и какой следует применить подход, чтобы не раздуть приложение и при этом сохранить баланс между раздутием и экономией на спичках.
Самым высоким уровнем абстракции View можно назвать Region который сам по себе то и не является полноценной View а лишь предоставляет интерфейс для отображения какой-то view. Вы можете подписываться не действия региона, реагировать на show, hide, render, swap и другие события, когда стоит применять регионы? Я рекомендую применять их только в том случае, когда в данном контексте подразумевается, что в этом месте может быть отображена другая View. К примеру в своих приложениях я использую на верхнем уровне один регион layout внутри которого может быть показана любая View от страницы с ошибкой до любого другого интерфейса. Например в главном интерфейсе пользователя PageView который унаследован от Mn.LayoutView есть 2 View это SidebarView унаследованная от Mn.AbstractView и регион page в котором отображаются Views разделов из Sidebar. Таким образом я подразумеваю, что sidebar у меня в рамках главной View неизменен, а в регионе page views будут меняться и представлять из себя разные другие Views. На мой взгляд, на уровне кода, абстракции и логики это достаточно лаконичное описание и сам подход к разработке приложения. В итоге я могу обратиться например так App.layout.currentView.sidebar и к текущей pageView App.layout.currentView.page.currentView как говорят — почувствуйте разницу.
Также вам скорее всего придется показывать различные Collections и для этого уже подготовлена для вас Mn.CollectionView которая принимает Collection, childVIew — View которая должна использоваться для отображения конкретной модели. В свою очередь для отображения модели уже подготовлена для вас Mn.ItemView. Стоить обратить внимание, что Mn.ItemView можно использовать не только внутри Mn.CollectionView но и как самостоятельную View представляющую какую-то одну модель данных. Если вам необходимо отобразить коллекцию + например фильтр для нее или еще какие-то доп данные. Можно использовать Mn.CompositeVIew которая помимо функций работы с collection у Mn.CollectionView также работает еще и с model. Ну а если хочется совсем сложных изысков, abstract view и регионы вам в этом помогут. Стоит также отметить, что не редки случаи использования в качестве View того же Knockout или React есть и такой симбиоз, правда с Backbone и без Marionette, правильной архитектуре такой союз никак не повредит и имеет право на жизнь.
Ближе к HTML
Теперь давайте рассмотрим View уже чуть ближе к интерфейсам и работе с html. Сама по себе View это по сути node element который мы создаем в JavaScript с помощью document.createElement В Marionette на уровне архитектуры, подразумевается, что у типичной View должен быть реализован метод render который связывает между собой данные модели и сам шаблон. Обычно, сама модель ничего не знает о той View, что ее показывает, а вот View имеет ссылку на model или viewmodel (в зависимости от вашего подхода варианты могут быть разные) которую она сейчас представляет. По своей сути метод render выглядит вот так
render: function() {
//Просто обновляем html шаблона на основе template передавая атрибуты модели
this.$el.html(this.template(this.model.attributes));
}
Теперь посмотрим как может выглядеть template
//А вот так может выглядеть template вашей view
template: function (modelAttrs) {
//Я в основном пользуюсь twig'ом
//Но тут может быть например и underscore template
//да что угодно
return twig.render({
id: 'someTemplate',
data: modelAttrs
})
}
Возможность переопределить стандартное поведение рендеринга данных дает разработчику огромную гибкость в плане контроля частоты обновления DOM и сокращения событий reflow и repaint в браузере. + возможность кеширования шаблонов дает ощутимый прирост в скорости сбора templates.
Также из удобств marionettе предоставляет сахарок в виде объектов events и ui, где мы можем сохранить ссылки на элементы view для сокращения выборки в DOM а также лаконичное описание
var MyView = Marionette.View.extend({
// ...
ui: {
"submit": "#submitForm"
},
events: {
"click @ui.submit": "submitForm" //аналогично click #submitForm
}
});
А что если без SPA?
Немного отвлечемся от модного SPA. Предположим вы работаете на каком-нибудь среднестатистическим проекте на Yii или Symfony, где подходы SPA изначально не практикуются т.к. в этой среде принято делать фронт немного иначе и почти вся это философия с логикой Views никому нафиг не нужна. Легче и быстрее jQuery спаггети стайл или symfony\yii way Но на самом деле можно поддерживать приятный фронт с все той же иерархией Views рассматривая раздельные страницы как некие singletones живущие сами по себе и при инициализации не обязательно связанные с основной App как-то. Имеющие лишь ссылку через window.App при необходимости доступа к другим View из соседних js файлов. (конечно за порядком загрузки js следить никто не отменял в этом случае).
var PageView = Marionette.View.extend({
el: '.page__wrapper',
ui: {
"submit": "#submitForm"
},
events: {
"click @ui.submit": "submitForm" //аналогично click #submitForm
},
initialize: function () {
//привязываем ui к существующему DOM
this.bindUIElements();
}
//....
//Пользуемся marionette Далее как нам нужно поверх интерфейса сгенерированного бекенд фреймворком
});
На мой взгляд это замечательная возможность скрестить два подхода, конечно если есть необходимость в этом. На своей практике сталкивался не раз и данный подход позволял мне сохранять родное для меня окружение работая с чужой технологией.
Если вы дочитали до этих строк, надеюсь мне удалось заинтересовать вас одним их подходов к построению современного фронтенда) буду рад ответить на комментарии и дополнить статью.
Последняя редакция 26 января, 2023 в 10:01