?

Log in

No account? Create an account
November 2016   01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30

4. BackboneJS.

Posted on 2015.07.10 at 16:50
Tags: , ,
Допустим, у нас есть группа разработчиков на PHP, которые знают чуть чуть JS и jQuery. Что самое простое мы можем сделать, чтобы начали писать браузерное приложение, и были продуктивны немедленно?

Мы можем попробовать использовать тот же принцип, и те же архитектурны правила, к которым они привыкли в PHP. У них были шаблоны с встроенным PHP? Отлично, мы будем использовать такие же шаблоны со встроенным JS, которые будут разворачиваться в браузере. Они использовали jQuery? Отлично, мы сохраним jQuery.

Нам остается задать этому какую-то структуру. Элемент UI - это будет объект View с присоединенным DOM-элементом, и методом 'render', который должен разворачивать шаблон в присоединенный элемент. Помимо этого, View должен уметь перехватывать события UI, и вызывать свои методы.

Помимо этого, нелишне добавить к этому простые средства для работы с REST API. Класс для кусочка JSON, который умеет создаваться/читаться/сохраняться/удаляться (CRUD) в каком-нибудь стандартном варианте REST. Назовем его Model. Раз у нас REST, то надо еще уметь получать их список. Значит, нужен еще Collection.

У нас сейчас получилось что? Правильно, самый популярный фреймворк для разработки в браузере - backbonejs (http://backbonejs.org), который, в сочетании с модульной системой позволяет из говна и палок собрать браузерное приложение.


Что мы видим глядя на архиутектуру Бэкбон? Два слоя, вью и модели. Однако, и вью, и модели совершенно "плоские". Логика авторов вполне понятна - в методе 'render' одного view совсем несложно подцепить другой view. Зачем навязывать людям какой-то способ это делать - пусть сами как хотят. А для точки протокола REST, которую представляет собой "модель", никакой "рекурсивности" и не нужно - она одна, и все. Хочется хранить в атрибуте модели сложный JSON? Да на здоровье.

Здесь мне полагается начать перебирать буковки чтобы описать паттерн, предоставляемый backbone - это MVC, MVVM, или что? На случай, если вам это действительно важно, какими буковками это называть (видел я таких, как определятся с буковками - сразу успокаиваются, начинают дышать ровнее, как будто поняли что-то. Или вбрасывают так - "это же MVVM!", и смотрят на тебя стеклянным глазом), то вообще-то это типичный PAC (https://en.wikipedia.org/wiki/Presentation–abstraction–control). Presentation - это шаблон, Abstraction - это модель или коллекция, а Control - это собственно сам View.

Не? Ну, тогда типичный MVP (https://en.wikipedia.org/wiki/Model–view–presenter), где презентер это опять вью, а вью - это шаблон. Не? Ну тогда я просто объясню, как оно работает :)

А на самом деле, работает оно следующим образом. Вью может как само создавать свою модель, так и получать ее снаружи. Модель умеет бросать события об изменениях, так что вью полагается на них подписываться, и вызывать себе render, внутри которого он должен развернуть в свой элемент свой шаблон с этой моделью в качестве контекста. И еще что-нибудь сделать - jQuery плагины инициализировать, и вложенные вью подцепить. Вот и все.

Зачем события? А вот тут мы приходим к фундаментальной проблеме веб платформы, предложить какое-то свое видение решения которой просто обязан каждый фреймворк. Штука в том, что JS исторически был однопоточен, и ввод-вывод из него делается асинхронно. То есть, когда вы запросили данные - вам их сразу в этом месте никто не даст. Сразу вы оставляете коллбэк, и они придут потом. И когда они придут - вы должны обновиться. Оставлять коллбэк невероятно неудобно, и разные фреймворки по разному подходят к этой проблеме.

Backbone использует очень простой подход. Мы пишем приложение, как будто данные у нас всегда есть, и как бы синхронно. Но - они по разным причинам могут обновляться (то, что они с сервера пришли - это частный случай), и мы всего-то должны - замечать это, и обновить UI. Поэтому, модели в backbone умеют бросать события 'change', а вью полагается его ловить и обновлять себя, вызывая 'render'. Выглядит это примерно вот так:

this.listenTo( this.model, 'change', this.render );
model.fetch();


Есть, конечно, и другие подходы. Например, вместо событий, можно использовать Promise.

model.fetch().done( function(){
    this.render();
}.bind( this ) );


Или, так сказать, опустится до коллбэка.

model.fetch({
    success : function(){
        this.render();
    }.bind( this )
});


Но для бэкбона идеоматической является модель программирования с событиями 'change'. И это - пожалуй, самая лучшая идея из всех, что есть в backbone.

Очень сложно говорить о достоинствах или недостатках этого фреймворка. По сути ведь, он решает не столько техническую проблему, сколько человеческую, которая сейчас уже теряет свою остроту. Он дает человеку с опытом традиционной веб-разработки такие правила проектирования, в рамках которых он может начать делать браузерные приложения немедленно, опираясь на свои существующие знания.

Сейчас технологии разработки SPA ушли очень далеко вперед, и backbone явно устарел на фоне современных фреймворков. Его суровый солдатский дизайн не дает разработчику почти никаких сервисов. Но одно бесспорное достоинство backbone отметить надо. Дело в том, что техника, которую бэкбон предлагает вам для рендеринга HTML, а это как-то так:
render : function(){
    this.el.innerHTML = this.template( this.model );
}

принципиально самый быстрый способ рендеринга HTML из всех возможных. Он не просто "быстрее" чем всякие там Angular, React, и Ember, он их в тряпки нафиг рвет. Присваивание в innerHTML в десятки раз быстрее, чем DOM-манипуляции. В десятки, Карл.

И если вы делаете мобильное приложение, то, в принципе, это очень даже повод задуматься.

Comments:


Nikita Prokopov
nikitonsky at 2015-07-10 18:46 (UTC) (Link)
А откуда информация про превосходство innerHTML? Речь ведь именно про браузерные механизмы (innerHTML vs appendNode), а не тяжеловестность обвеса фреймворков вокруг них?

Все, что я нашел подтверждающего, относится к IE 5.5-7, на всех более современных браузерах, включая мобилки, разница не заметна (в пределах 10%). Более того, это равновесие периодически смещается туда/обратно.

http://www.quirksmode.org/dom/innerhtml.html
http://andrew.hedges.name/experiments/innerhtml/
http://blog.mikie.iki.fi/2014/05/innerhtml-vs-appendnode-vs.html
Gaperton
gaperton at 2015-07-11 07:49 (UTC) (Link)
Думаю, мне придется опять собрать свой тест, как я это делал несколько лет назад. Когда я измерял, разница была в десятки раз. Могло, конечно, что-то поменяться с тех пор, но это не правильно измерять микротестом, как в приведенных статьях, добавляя один div.

Когда мы в бэкбон разворачиваем шаблон в текст, и присваиваем его в innerHTML, во-первых, обработки событий не трогаются вообще, потому, что они висят на корневом узле, а во-вторых, рендеринг и расчет лэйаута происходит один раз.

Когда мы вставляем узлы по одному, происходит многократный рендеринг видимой области, потенциально - с перестройкой лэйаута. Эти микротесты устроены так, что они изолируют эту проблему, и это незаметно. Если же мы начнем ковыряться где-нибудь в недрах сложного и красивого документа, расклад должен быть уже совсем другим. Или, например, добавление одной строки таблицы в простом случае приводит к вычислению размеров ее колонок заново, так, что рендеринг через добавление узлов получает сложность O(N^2).

Сейчас без всякого теста - что я вижу в своем приложении, которое во многих местах делает render при обновлении в стиле бэкбон. Он делается моментально, пока я не добавляю в шаблон кастомные тэги x-tag, которые разворачиваются через DOM-манипуляцию. И вместо кина я вижу слайд-шоу. Я искренне надеюсь, что дело здесь не только в DOM-манипуляции, иначе нам не поможет выпиливание x-tag и замена view-layer на React.

И на то, что React делает ее не так тупо. React, по крайней мере, не будет делать лишних манипуляций, когда ничего в DOM не поменялось.

Edited at 2015-07-11 10:37 am (UTC)
Nikita Prokopov
nikitonsky at 2015-07-11 10:53 (UTC) (Link)
Вроде бы если вставить N узлов внутри одной js-функции, и не спрашивать в процессе у вставленных элементов clientWidth/Height/scrollOffset и прочие штуки, которые триггерят лайаут, рендеринг тоже вызовется один раз, по выходу из функции.

А что за обработки событий, которые висят на корневом узле?
Gaperton
gaperton at 2015-07-11 11:07 (UTC) (Link)
> Вроде бы если вставить N узлов внутри одной js-функции, и не спрашивать в процессе у вставленных элементов clientWidth/Height/scrollOffset и прочие штуки, которые триггерят лайаут, рендеринг тоже вызовется один раз, по выходу из функции.

Это лучше, но все равно плохо. Особенно, если хочется мелкие виджеты использовать, здесь явно не обойдешься одной функцией. x-tag и polymer, например, не могут, и разворот тэгов в реальном приложении тормозит нечеловечески.

Если бы был явный интерфейс, чтобы делать транзакции на DOM-дереве, это бы помогло. Что, в общем, и происходит при присваивании innerHTML. Он и есть такой интерфейс, пусть немного кривой. Впрочем, почему немного. Сильно кривой.

> А что за обработки событий, которые висят на корневом узле?

При повторном рендеринге, когда DOM-дерево удаляется, браузер снимает обработчики событий с элементов поддерева, и если их много - это реально медленно.

Бэкбон же вешает обработчики на корневой элемент, которому меняет innerHTML, так что при обновлении не надо ни от чего отписываться, и все происходит быстро. Это не имеет большого значения, если использовать DOM-манипуляцию, насколько я понимаю - ничего не удаляется. А если имеет - никто не мешает делать так же.

Edited at 2015-07-11 11:10 am (UTC)
Nikita Prokopov
nikitonsky at 2015-07-11 11:35 (UTC) (Link)
> Это лучше, но все равно плохо. Особенно, если хочется мелкие виджеты использовать, здесь явно не обойдешься одной функцией.

Речь об одной top-level функции, внутри вложенность может быть какая угодно.

Кстати я правильно понимаю что backbone это только initial rendering, живое приложение на нем не сделать? Скажем, если у корневого элемента добавился класс, весь шаблон перерендеривать и переприсваивать innerHTML же не станешь?

> x-tag и polymer, например, не могут, и разворот тэгов в реальном приложении тормозит нечеловечески.

Это проблема конкретных фреймворков, не самого DOM API?
Gaperton
gaperton at 2015-07-11 17:15 (UTC) (Link)
>> x-tag и polymer, например, не могут, и разворот тэгов в реальном приложении тормозит нечеловечески.

> Это проблема конкретных фреймворков, не самого DOM API?

Когда я увижу конкретные фреймворки, полагающиеся на DOM API, лишенные этих проблем в реальном приложении, не на микротестах, я соглашусь. Пока чо-та везде одни проблемы конкретных фреймворков, да куча советов как это ускорить, сводящихся к "не рендери слишком много". А быстро все только теоретически.

Edited at 2015-07-11 05:19 pm (UTC)
Gaperton
gaperton at 2015-07-11 17:24 (UTC) (Link)
И кроме того, про то, что браузер обновляет DOM только при попадании в глобальный цикл, здорово бы было увидеть пруфлинк. Еще несколько лет назад я в тестах своими глазами видел, что это не так.
tretiy3
tretiy3 at 2015-07-10 22:33 (UTC) (Link)
бекбон не нужен. вообще. совсем.
и все разговоры про буковки- MVC, MMVC и все их комбинации - ничего этого не нужно. глупости. почему-то принято в вебе делать это ваше model-view-controller но ни один человек еще, нормально, не смог объяснить что это и для чего оно нужно. а все-таки все поперли засовывать эту концепцию в веб (рельсы) и в одностраничные приложения (бекбон). а то, что никакого смысла нет в таком разделении - да и черт с ним. идиоты...
brightist
brightist at 2015-07-11 02:59 (UTC) (Link)

если вы не понимаете зачем оно нужно, то правда это очень печально

tretiy3
tretiy3 at 2015-07-11 08:37 (UTC) (Link)
можете объяснить что такое контроллер и зачем он нужен?
brightist
brightist at 2015-07-11 14:15 (UTC) (Link)

мои соболезнования :)

tretiy3
tretiy3 at 2015-07-11 15:27 (UTC) (Link)
не за что
brightist
brightist at 2015-07-11 02:58 (UTC) (Link)

а что тогда все поперлись на ангулар? только потому что от гугла?

shabunc
shabunc at 2015-07-11 03:08 (UTC) (Link)
потому что в Бекбоне нет датабайндинга в обе стороны из коробки, а в ангуляре есть. Это была киллер фича.
Gaperton
gaperton at 2015-07-11 10:06 (UTC) (Link)
Ну как-то да. Ангулар из коробки позволяет делать простые вещи очень просто, и это людям нравится.

Бэкбон же позволяет делать вещи привычным образом. Это другой вид простоты, штоле. И это, кстати, совсем не значит, что этот способ жутко хорош и удобен. В следующем посте я подробно расскажу с примерами, какие проблемы возникают при разработке больших приложений с бэкбон.

Previous Entry  Next Entry