3 js-фреймворка на личном опыте

8 февр. 2013 г. | | |

Разрабатывая веб-приложение (не путать с веб-сайтом с интерактивным UI) на jQuery, Zepto или другом UI-ном фреймворке, довольно быстро приходишь к изобретению MV*-велосипеда, сначала  отделяя данные от UI-части приложения, а потом назначая ответственного за взаимодействием между ними. Резонный вопрос: а какие существуют готовые решения, облегчающие взаимодействие UI, данных и логики, поверх jQuery (Zepto)? И как ответ недавно мне попалась статья "A Collection Of Javascript MVC Frameworks That You Should Know About". Знаете, сколько там фреймворков? Больше 30! Как же сделать правильный выбор? Выбор фреймворка - это серьезное решение, от которого не последним образом зависит будущее проекта. На решение каких проблем большим образом ориентирован фреймворк? Какой из MV* подходов используется во фреймворке (и стоит ли на этом заморачиваться)? Сильно ли тяжеловесен фреймворк? Сколько дополнительных библиотек он тянет за собой? Насколько быстро и легко разобраться с устройством фреймворка? Насколько хороша документация? Есть ли дружное сообщество вокруг библиотеки? Как не ошибиться, ведь все фреймворки похожи друг на друга, и каждый обещает сделать всю работу за меня? Да никак. Есть лишь несколько подсказок и советов от умудрённых опытом людей и сделанные на основе собственного опыта выводы.

Знакомство 1

Первый, с кем я познакомилась из фреймворков, был Backbone.js. Backbone.js может похвастаться внушительным списоком приложений, использующих фреймворк, и  кучей примочек вроде Backbone.Marionette, Backbone.ViewMaster и Backbone.stickit. Хотя такие примочки являются своего рода знаком, что какие-то вещи фреймворк не решает или решает плохо. В упакованном виде Backbone.js весит всего 6.3Кб,  а единственная жесткая зависимость - это Underscore.js. Но если хочется использовать всю мощь Backbone.Router и иметь возможность манипулировать с DOM в Backbone.View, то без json2.js и jQuery (или Zepto) не обойтись. Backbone сразу же поразил меня своим мощным Backbone.Router, моделями и коллекциями. Я была неопытная, впечатлительная, он был моим первым javascript mv*-фреймворком.
Я могла описать модель данных (Backbone.Model)

//backbone.js 
var FrameworkModel = Backbone.Model.extend({
        defaults: {           
            "title": "",
            "url": "",
            "shortDescription" : ""
        }
    });

сделать на её основе коллекцию
//backbone.js 
var FrameworkCollection = Backbone.Collection.extend({
        model: FrameworkModel
    });

показать html-шаблон элемента (Backbone.View)
//backbone.js 
var FrameworkView = Backbone.View.extend({
      tagName: "div",  //тэг элемента
      className: "line", //css-класс для тэга
      template: $("#itemTemplate").html(), //html шаблонного div'a

      render: function() {
        var templ = _.template(this.template);
        this.$el.html(templ(this.model.toJSON()));
        return this;
      }
    });

//html скелет
<script id="itemTemplate" type="text/template">                       
      <span id="title"><a href="<%= url %>"><%= title %></a></span>

       <span id="description"><%= shortDescription %> </span>
</script>
 
и легко получить отображение всей коллекции:
а, если немного поработать с представлением (FrameworkView), то можно отлавливать изменения данных в модели и на лету обновлять форму.
//backbone.js 
var FrameworkView = Backbone.View.extend({
      /*...*/
      events: {          
            "dblclick span#description" : "edit", //метод влючает режим редактирования
            "keypress input#description" : "update" //метод сохраняет результат
      },
      initialize: function()
      {

          //подписываем на изменения в модели рендер представления
          this.model.bind('change', this.render, this);
      },
      render: function() {
        //рендер представления из модели
      },
      update: function(e){

        //обновление модели 
        //метод save вызывает событие change, на которое подписан наш метод render
          this.model.save({shortDescription: this.$('input#description').val() });
      },
      edit: function() {      
        //режим редактирования
      }
    });

Знакомство 2

Потом я познакомилась с Knockout.js. С ним было всё иначе: декларативное связывание, автоматический апдейт UI. Он не требовал от меня наследоваться от своих классов, а ненавязчиво предлагал свои observable и computed возможности. Это было одновременно элегантно и мощно.
//knockout.js 
//модель 
function FrameworkModel (_title, _url, _shortDescription) {
            var self = this;
            self.title =
ko.observable(_title);
            self.url =
ko.observable(_url);
            self.shortDescription = ko.observable(_shortDescription);
            self.isEditing = ko.observable(false);
            self.toogleEdit = function()
            {
              self.isEditing(true);
            }
            self.finishEdit = function()
            {
                self.isEditing(false);            
            }
      }

//отображение
function FrameworkView () {
      var self = this;
      self.newFramework = ko.observable(new FrameworkModel("", "", ""));
      self.frameworks = ko.observableArray([]);
      self.addFramework = function()
      {
        self.frameworks.push(new FrameworkModel( self.newFramework.title,
                                                 self.newFramework.url, 
                                        self.newFramework.shortDescription));
        self.newFramework.title = "";
        self.newFramework.url = "";
        self.newFramework.shortDescription = "";
      }     
    }

//магия
ko.applyBindings(new FrameworkView());  
Knockout просто делал то, что я хотела. С ним было легко и просто, хоть и по-другому. В отличие от Backbone.js  он ограничивал возможность работы с html-элементами в js-представлении, вынося это всё в разметку. Например, вот шаблон для отображения объекта.
//knockout.js 
<script id="itemTemplate" type="text/html">
     <div class="line" data-bind="css: {editable:isEditing}">
     <span id="title"><a href="" data-bind="text: title, href: url"></a></span>
     <span id="description" data-bind="text: shortDescription(), click: toogleEdit"></span>
     <input id="description" type="text" value='' data-bind="value: shortDescription, enterKey: finishEdit" />
     </div>
</script>
Для того, чтобы отобразить список элементов, на Backbone.js нужно создать дополнительно отображение для коллекции элементов, и уже в нём в цилке отрендерить каждый объект.
//backbone.js
var FrameworkCollectionView = Backbone.View.extend({
      el: $("#list"), //указываем элемент, в который будут помещаться отрендеренные объекты
      initialize: function() {
        /*...*/
        this.render();
      },
      render: function() {
        this.collection.each(function(item) {
          this.renderItem(item);
        }, this);
      },
      renderItem: function(item) {
        var itemView = new FrameworkView({ model: item });
        this.$el.append(itemView.render().el);
      }, 

/*...*/ 

На knockout.js всё, что мне необходимо сделать, это в html-разметке элементу, в котором я хочу видеть список, указать data-bind:
//knockout.js
<div data-bind="template: { name: 'itemTemplate', foreach: frameworks }"></div>
Т.е. js-код по минимуму завязан на html-разметку. И это здорово. Knockout.js помогает думать немного в другом стиле, забыть о привязке данных к разметке и работать над логикой. И это, опять-таки, здорово! Backbone.js оставлял полностью на меня задачу обновления изменённых данных в модели / представлении. С knockout.js об этом даже не приходилось задумываться. Он действительно решал одну из моих  проблем.

Знакомство 3

А потом случился AngularJs. Фреймворк от Google. Я не знаю, что ещё добавить. Первое, что отличает его от двух предыдущих фреймворков - это туториал, в котором расказывается, как тестировать код, написанный под Angular.js с помощью Jasmin. Гугл не подвёл.
Мне нравится идея вложенных окружений (scope), которая является основой Angular.Js:
a scope can be seen as the glue which allows the template, model and controller to work together. Angular uses scopes, along with the information contained in the template, data model, and controller, to keep models and views separate, but in sync. 
И дальше - самое прекрасное: двусторонне связывание данных!
Any changes made to the model are reflected in the view; any changes that occur in the view are reflected in the model.
Следующее, чем прекрасен Angular.js - он много чего умеет из коробки. Например, легко делаются такие рутинные вещи, как фильтрация и сортировка.
//html
    <div>Search: <input ng-model="query" />    </div>
    <div ng-repeat="framework in frameworks  | filter:query">
               /* ... */    </div> 
Вообще, с Angular.js ты пишешь код и только спустя какое-то время понимаешь, что кроме ангулара ничего больше из библиотек в проекте нет. Даже jQuery не подгружен! И это прекрасно. Но если нужно сделать какуют-то обычную вещь, например, отследить нажатие Enter в текстовом поле, то придется либо подключать angular-ui + jquery, либо писать свою директиву (на самом деле knockout этим тоже грешен):
//js
angular.module("frameworkApp", []).directive('onEnter',
  function() {
  return function( scope, elem, attrs ) {         
      elem.bind('keyup', function(e) {          
        if (e.keyCode === 13)
        {     
          scope.$apply(attrs.onEnter);
        }
    });
  };
});

//html 
<input id="description" type="text" value='' ng-model="framework.shortDescription" on-enter="framework.finishEdit()" /> 

Заключение

Хороший инструмент отличается от плохого решением поставленной проблемы и простотой. С этой стороны мне очень понравился AngularJS. Он немного сложнее, чем KnockoutJS, но большой проект я бы, не задумываясь, начала на Angular. Но для небольшого проекта я бы выбрала Batman.js, который не попал в этот обзор, но о котором я ещё расскажу.  Но нет ничего лучше для того, чтобы понять, насколько удобно работать с фреймворком,  чем взять и поработать с ни: например, написать с его помощью какое-нибудь несложное приложение. И на основе полученных эмоций в ходе разработки, выбрать понравившийся. Поняно, что с таким зоопарком всех фреймворков не перепробуешь - специально для этого был создан проект TODO MVC, где собраны реализации todo-списка с помощью разных js-фреймворков.
P.S. Несколько дней назад twitter пополнил ряды js-фреймфорков, выпустив свой собственный фреймворк Flight, который, конечно же, совсем другой, но пытается решить всё те же проблемы.

Ссылки

http://backbonejs.org - оффициальный сайт backbone.js [en]
http://backbonejs.ru - русскоязчная документация по backbone.js
http://knockoutjs.com - оффициальный сайт knockout.js [en]
http://learn.knockoutjs.com - прекрасный интерактивынй туториал по knockout.js
http://knockback.js - backbone.js и knockout.js в одном флаконе: объеденены сильные стороны обоих фреймворков в ещё один фреймворк.
http://angularjs.org - официальная документация по angular.js
http://egghead.io/ - видеокурс по angular.js
Отличный обзор JS-фреймворков на хабре (часть 1 и часть 2)

4 коммент.:

Евгений комментирует...

Спасибо, Полезно!

Родион комментирует...

Спасибо!!!

Евгений комментирует...

к Backbone больше не вернетесь?

Shemyakina Tatiana комментирует...

Евгений, я последнее время пишу на C# под .net
Больше всего понравился нокаут, на нём удобно и быстро развернуть небольшое фронтэнд-приложение. Вместо бэкбона смотрела бы на ангулар.

Отправить комментарий