strokoff

Про Marionette.js

marionetteВ сети сейчас можно найти много статей про модные ныне 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