Про 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