Опыт разработки эффективного SPA

35
Опыт разработки эффективного SPA Евгений Абросимов ByndyuSoft twitter.com/abrosimov 7-я конференция .NET разработчиков 22 сентября 2013 www.dotnetconf.ru

description

Доклад с седьмой конференции DotNetConf

Transcript of Опыт разработки эффективного SPA

Page 1: Опыт разработки эффективного SPA

Опыт разработки

эффективного SPA

Евгений Абросимов

ByndyuSoft

twitter.com/abrosimov

7-я конференция .NET разработчиков

22 сентября 2013

www.dotnetconf.ru

Page 2: Опыт разработки эффективного SPA

2 Опыт разработки эффективного SPA, Евгений Абросимов

Немного истории

(function($, window, document, undefined) { 'sue strict'; $('#someElement').somePlugin({ option1: value1, ... opntionN: valueN }); $('.someClass').doSomething(); $('#someContainer').on('click', '.elemInside', function(e) { e.stopPrapagation(); // Прочие действия }); })(jQuery, this, document);

Page 3: Опыт разработки эффективного SPA

3 Опыт разработки эффективного SPA, Евгений Абросимов

Немного истории

var $app = $app || {

core: {},

ui: {},

config: {},

utilities: {},

login: {},

registration: {},

//...

};

Page 4: Опыт разработки эффективного SPA

4 Опыт разработки эффективного SPA, Евгений Абросимов

Что такое SPA?

Single Page Application – это приложение, которое выполняется в браузере. SPA базируется на HTML/JS/CSS/JSON. SPA жестко делит клиентскую и серверную логику. SPA общается с сервером только чистыми данными. Разметка хранится на стороне клиента в шаблонах. Перезагрузки страницы не происходит.

Page 5: Опыт разработки эффективного SPA

SPA vs MPA

Page 6: Опыт разработки эффективного SPA

6 Опыт разработки эффективного SPA, Евгений Абросимов

Примеры SPA

Page 7: Опыт разработки эффективного SPA

7 Опыт разработки эффективного SPA, Евгений Абросимов

Критерии эффективности

1. Модульность системы.

2. Однозначное определение местоположения

пользователя роутом.

3. Событийная модель коммуникации.

4. Минимизированное количество изменений DOM.

5. Отсутствие утечек памяти.

6. Минимизированное количество запросов к серверу.

Page 8: Опыт разработки эффективного SPA

8 Опыт разработки эффективного SPA, Евгений Абросимов

Фундамент для эффективного SPA

1. Минимальное время для старта SPA. Минимум запросов к серверу за ресурсами: images.png, style.css, script.js.

2. Эффективная верстка. Верстка должна разделять идентификаторы и классы, которые отвечают

за логику работы, и классы, которые отвечают за отображение макета

приложения в браузере. Идеально: логика на id, верстка на class.

Использование быстрых селекторов. Минимизация объема разметки.

3. Хороший Javascript

Page 9: Опыт разработки эффективного SPA

9 Опыт разработки эффективного SPA, Евгений Абросимов

Backbone.js

Это javascript-библиотека, которая реализует паттерн MVW

(Model-View-Whatever).

Создана была Джереми Ашкенасом (DocumentCloud,

CoffeeScript, Underscore).

Первый релиз был выпущен 13 октября 2010 года.

Актуальная версия: v1.0.0 (20 марта 2013)

forks: 3236 / stars: 15723

Зависит от: jQuery(Zepto) и Underscore.js

http://backbonejs.org/docs/backbone.html

Page 10: Опыт разработки эффективного SPA

10 Опыт разработки эффективного SPA, Евгений Абросимов

Сущности backbone.js Backbone.Model

1. Model – это единица данных. Отвечает за получение, отправку,

хранение, валидацию и прочие манипуляции с данными какой то

сущности.

var sampleModel = Backbone.Model.extend({ url: '/path/to/data', defaults: { //Значения атрибутов по-умолчанию }, initialize: function () { //Конструктор модели }, someMethod: function () { //Тело метода } });

Page 11: Опыт разработки эффективного SPA

11 Опыт разработки эффективного SPA, Евгений Абросимов

Сущности backbone.js Backbone.Collection

2. Collection – это массив или список моделей. Отвечает за получение и

отправку набора данных какой то сущности, а так же за манипуляции с

моделями (создание, обновление, удаление). Работает только с

моделями определенного типа.

var sampleCollection = Backbone.Collection.extend({ url: '/path/to/data', model: sampleModel, initialize: function () { //Конструктор коллекции }, filteredByName: function (name) { return this.filter(function (model) { return model.get(‘name') === name; }); }, });

Page 12: Опыт разработки эффективного SPA

12 Опыт разработки эффективного SPA, Евгений Абросимов

Сущности backbone.js Backbone.View

3. View – это представление модели или коллекций. Отвечает за

рендеринг модели или коллекции, работу с шаблонами, обработку

событий и другое.

var sampleView = Backbone.View.extend({ tagName: 'div', className: 'someDiv', initialize: function () { //Конструктор представления this.model.on('change', this.render, this); }, render: function () { var data = this.model.toJSON(); this.$el.html(_.template(this.template, data)); return this; } });

Page 13: Опыт разработки эффективного SPA

13 Опыт разработки эффективного SPA, Евгений Абросимов

Сущности backbone.js Backbone.Router

4. Router предоставляет методы для маршрутизации на стороне

клиента, а также связывания этих действий с событиями.

var sampleRouter = Backbone.Router.extend({ routes: { //Словарь роутов и экшенов 'index': 'index', 'search/:id': 'search' }, initialize: function () { //Конструктор роута }, index: function () { //Тело метода }, search: function (id) { //Тело метода } });

Page 14: Опыт разработки эффективного SPA

14 Опыт разработки эффективного SPA, Евгений Абросимов

Модульная структура

Require.js – это javascript-библиотека, которая

реализует подход Asynchronous Module Definition.

define() – описание модуля.

require() – подтягивание зависимости.

http://requirejs.org/ v2.1.8 / forks: 693 / stars: 4528

<script type="text/javascript" src="assets/require/require.min.js" data-main="app/packages.js"></script>

Page 15: Опыт разработки эффективного SPA

15 Опыт разработки эффективного SPA, Евгений Абросимов

Модульная структура

или

define('mymodule',['jquery'], function ($) { return { foo: 'bar' }; } );

define('mymodule', function (require) { var $ = require('jquery'); return { foo: 'bar' }; } );

Page 16: Опыт разработки эффективного SPA

16 Опыт разработки эффективного SPA, Евгений Абросимов

Событийная модель коммуникации

Как обеспечить коммуникацию модулей?

Использовать события!

$('#someElement').on('someEvent', function () { //Тело обработчика }); ... $('#someElement').trigger('someEvent');

Анти-паттерн!

Page 17: Опыт разработки эффективного SPA

17 Опыт разработки эффективного SPA, Евгений Абросимов

Событийная модель коммуникации Backbone.Events

var vent = _.extend({}, Backbone.Events);

vent.on('smth', function (data) {

console.log(data); //Object {hello: "world"}

});

vent.trigger('smth', { hello: 'world' });

vent.off('smth');

Backbone.Events – это встроенные в backbone объект, который реализует паттерн Наблюдатель (Observer).

Page 18: Опыт разработки эффективного SPA

18 Опыт разработки эффективного SPA, Евгений Абросимов

Событийная модель коммуникации Backbone.Wreqr

Это расширенная версия Backbone.Events.

https://github.com/marionettejs/backbone.wreqr

v0.2.0 / Forks: 14 / Stars: 126

Состоит из:

1. EventAggregator

2. Commands

3. Request/Response

Page 19: Опыт разработки эффективного SPA

19 Опыт разработки эффективного SPA, Евгений Абросимов

Событийная модель коммуникации Backbone.Wreqr.EventAggregator

Аналог Backbone.Events

var vent = new Backbone.Wreqr.EventAggregator(); vent.on("foo", function () { console.log("foo event"); }); vent.trigger("foo");

Page 20: Опыт разработки эффективного SPA

20 Опыт разработки эффективного SPA, Евгений Абросимов

Событийная модель коммуникации Backbone.Wreqr.Commands

Это реализация паттерна «Команда»

var commands = new Backbone.Wreqr.Commands(); commands.setHandler("foo", function () { console.log("the foo command was executed"); }); commands.execute("foo");

Page 21: Опыт разработки эффективного SPA

21 Опыт разработки эффективного SPA, Евгений Абросимов

Событийная модель коммуникации Backbone.Wreqr.RequestResponse

Реализация паттерна «Запрос/Ответ»

var reqres = new Backbone.Wreqr.RequestResponse(); reqres.setHandler("foo", function () { return "foo requested. this is the response"; }); var result = reqres.request("foo");

Page 22: Опыт разработки эффективного SPA

22 Опыт разработки эффективного SPA, Евгений Абросимов

Манипуляция с DOM

Перекомпоновка это процесс реконструкции части

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

Перерисовка это процесс повторного

отрисовывания элементов части дерева DOM,

которая была затронута. Если при изменении не была затронута геометрия элемента, то выполняется

только перерисовка

Минимизируйте количество перекомпоновок и перерисовок!

Page 23: Опыт разработки эффективного SPA

23 Опыт разработки эффективного SPA, Евгений Абросимов

Backbone.View

var someView = Backbone.View.extend({ initialize: function () { this.model.on('change', this.render, this); }, render: function () { this.$el.html(this.template(this.model.toJSON())); return this; } });

Что здесь неправильно?

Page 24: Опыт разработки эффективного SPA

24 Опыт разработки эффективного SPA, Евгений Абросимов

Backbone.View Сбор данных: two-way data bindings

1. Backbone.modelBinder

https://github.com/theironcook/Backbone.ModelBinder

v1.0.4 / forks: 154 / stars / 1077

<input type="text" name="address"/>

или

render: function(){ this.$el.html(_.template(this.template, this.model.toJSON())); this.modelBinder.bind(this.model, this.el); }

render: function(){ var bindings = { address: '[name=address]' }; this.$el.html(_.template(this.template, this.model.toJSON())); this.modelBinder.bind(this.model, this.el, bindings); }

Page 25: Опыт разработки эффективного SPA

25 Опыт разработки эффективного SPA, Евгений Абросимов

Backbone.View Сбор данных: two-way data bindings

2. Rivets.js + Adapter

http://rivetsjs.com/ v0.5.10 / forks: 96 / stars: 1084

<input type="text" name="address" data-value="p.address" /> <span data-text="p.address"></span>

render: function(){ this.$el.html(_.template(this.template, this.model.toJSON())); this.rivets.bind(this.el, {p: this.model}); }

Page 26: Опыт разработки эффективного SPA

26 Опыт разработки эффективного SPA, Евгений Абросимов

Управление памятью

Дано: имеем view с несколькими дочерними

views. Удаляем родительское view.

Вопрос: Что произойдет с дочерними views?

Ответ: В ваше приложение придут zombies.

Page 27: Опыт разработки эффективного SPA

27 Опыт разработки эффективного SPA, Евгений Абросимов

Управление памятью

Для того, чтобы избежать появления zombies нужно:

• перед удалением родительской view, удалять

дочерние views;

• отписываться от всех хендлеров событий

(this.model.off() или использовать this.listenTo(),

this.$el.empty());

• уничтожать запущенные виджеты Jquery UI;

• никогда не удалять контейнер одной view из

другой view.

Page 28: Опыт разработки эффективного SPA

28 Опыт разработки эффективного SPA, Евгений Абросимов

Управление памятью Zombies: Решение

var baseView = Backbone.View.extend({ nested: [], initialize: function () { Backbone.View.prototype.initialize.call(this); }, register: function (sub) { if (sub instanceof Backbone.View) { this.nested.push(sub); } }, remove: function () { var viewsCount = this.nested.length; if (viewsCount) { for (var i = 0; i < viewsCount; i++) { this.nested[i].remove(); } } this.$el.empty(); Backbone.View.prototype.remove.call(this); } });

Page 29: Опыт разработки эффективного SPA

29 Опыт разработки эффективного SPA, Евгений Абросимов

Client vs Server

Как сократить время коммуникации клиента и сервера? 1. Отдавать не разметку, а JSON. Разметка формируется на клиенте.

2. Не отправлять серверу лишние запросы. Пул ajax-запросов позволит получить клиенту только актуальные данные.

3. Не спрашивать у сервера то, что уже было получено ранее. Кешируйте то, что уже было получено ранее.

Page 30: Опыт разработки эффективного SPA

30 Опыт разработки эффективного SPA, Евгений Абросимов

Кеширование на клиенте

Model и Collection – две сущности,

созданные для хранения данных.

Model → Запись в таблице БД

Collection → Таблица БД

Page 31: Опыт разработки эффективного SPA

31 Опыт разработки эффективного SPA, Евгений Абросимов

Кеширование на клиенте SQL и Backbone.Collection

select * from messages where id = 0

var result = messages.where({ id: 0 });

insert into messages values ('0', 'Vasya', 'Petya')

messages.add(new message({ id: 0, from: 'Vasya', to: 'Petya'}));

delete from messages where id = 1

messages.remove(messages.where({ Id: 1 }))

Выборка

Вставка

Удаление

Page 32: Опыт разработки эффективного SPA

32 Опыт разработки эффективного SPA, Евгений Абросимов

Кеширование на клиенте Репозиторий

define(function () { var repository = function() { var tables = { }; function create(name, model) { if (!tables[name]) { tables.push(new Backbone.Collection({ model: model })); } } function insert(name, model) { if (table[name]) { tables[name].add(model); } } function remove(name, options) { if (tables[name]) { tables[name].remove(tables[name].where(options)); } } function select(name, options) { if (tables[name]) { return tables[name].where(options); } return false; } return { create: create, insert: insert, remove: remove, select: select } }; return new repository(); });

Page 33: Опыт разработки эффективного SPA

33 Опыт разработки эффективного SPA, Евгений Абросимов

Что дальше? Тестирование: TDD & BDD

1. QUnit / v1.12.0 / TDD

http://qunitjs.com/

2. Jasmine / v1.3.1 / BDD

http://pivotal.github.io/jasmine/

3. Mocha / v1.13.0 / BDD & TDD

http://visionmedia.github.io/mocha/

Page 34: Опыт разработки эффективного SPA

34 Опыт разработки эффективного SPA, Евгений Абросимов

Что дальше? Архитектурные фреймворки

1. Marionette / v1.1.0 / forks: 625 / stars: 3729

http://marionettejs.com/

2. Chaplin / v0.10.0 / forks: 220 / stars: 2321

http://chaplinjs.org

3. Thorax / v2.0.1 / forks: 82 / stars: 865

http://thoraxjs.org/

4. Giraffe / v0.1.3 / forks: 3 / stars: 66

http://barc.github.io/backbone.giraffe/

Page 35: Опыт разработки эффективного SPA

35 Опыт разработки эффективного SPA, Евгений Абросимов

Спасибо за внимание

Евгений Абросимов

ByndyuSoft

[email protected]

twitter.com/abrosimov