Робимо круті Single Page Application на basis.js. Частина 1, вступне-теоретична

Всім доброго часу доби!
Ця стаття розпочинає цикл публікацій, присвячених basis.js – фреймворку для створення повноцінних Single Page Application.


Про фреймворки

Ядро сучасних фронтенд–фреймворків практично не орієнтовані на роботу з даними так як не має структур і алгоритмів для обробки колекцій.
Замість цього, розробнику надається можливість самостійно працювати з даними і итерировать колекції прямо в шаблоні, або оперувати DOM–елементами прямо з коду контролера/компонента, знову ж таки, самостійно пов'язуючи дані з візуальним представленням.
Мінуси таких підходів повинні бути очевидні.
При маніпуляції з DOM–елементами з контролера/компоненти:
— доводиться самостійно реалізовувати структури даних і алгоритми обробки наборів даних
— збільшується складність і поддерживаемость коду, так як доводиться працювати вручну з DOM–елементами і налагоджувати зв'язки між даними і DOM–елементами
— найчастіше неможливо використовувати шаблонизаторы

При використанні шаблонизатора в роботі з набором даних:
— погіршується читаність шаблону, так як логіка частково переноситься в шаблон
— чи ефективно в шаблонизаторе реалізований механізм оновлення частин уявлень і реалізований?
— все ще відсутні механізми обробки наборів даних

Раніше все це практично не мало значення, так як за обробку даних та їх візуальне представлення відповідав backend.
Все, що потрібно було зробити – завантажити сторінку за потрібною адресою і подання вже сформовано.
Мінус такого підходу – відсутність інтерактивності і динамічності в плані оновлення даних.
Сучасний SPA має бути автономним у плані функціоналу і звертатися на сервер тільки тоді, коли необхідно синхронізувати дані (зберегти або отримати нові).
Відповідно, всю роботу з обробки і візуалізації цих даних повинен брати на себе фронтенд.
З приходом AJAX і WebSocket стало набагато простіше стежити за оновленням даних і забезпечити інтерактивність програми.
Але AJAX і WebSocket про роботу з мережею і вони не вирішують базової завдання – роботи з даними.

Припустимо, ви робите одностраничное додаток – клієнт для ВКонтакте.
Ось деякі з вимог до програми: завантаження, оновлення, пошук, групування, сортування друзів з соціальної мережі.
Потім додається розділ «музика». Тут вже потрібно працювати з плейлистами (як своїми, так і друзів). Той же пошук.
Додано розділ «повідомлення». Тут є робота з «кімнатами» і повідомленнями.
Думаю сенс зрозумілий…
Так ось: музика, друзі, повідомлення і так далі – це всі дані, які треба сортувати, групувати, шукати і, нарешті, візуалізувати. При цьому візуальне подання повинно своєчасно оновлюватися в режимі реального часу.

Таким чином, в арсеналі розробника повинні бути потужні і зручні інструменти для роботи, теоретично, з будь-якою кількістю даних.

Про шаблонизаторы

Щоб не розводити черговий холівар, я не буду приводити в приклад конкретні популярний фреймворки. Замість цього, уявімо, що є якийсь абстрактний фреймворк з гнучким і зручним шаблонизатором.
Як би ми вирішували завдання описаного вище SPA?
Дуже просто – завантажуємо дані з сервера соц.мережі і годуємо їх шаблонизатору фреймворка.
Відмінно, завдання виведення ми, здавалося б, вирішили.
Але ось ми розуміємо, що вже отрисованную колекцію додалися нові елементи.
Що робити?
Знову ж таки, все дуже просто – просимо шаблонизатор перемалювати подання, але вже з новими даними.
І все начебто добре, поки даних не так багато.
А тепер уявімо, що у нас є колекція з 100 об'єктів, яку ми отримали від сервера.
Ми віддали ці дані шаблонизатору, він їх слухняно відмалювати, але через деякі час, сервер повідомляє нам, що в колекцію додався ще один елемент.
Що ми робимо? Знову віддаємо дані шаблонизатору і він, увагу, оновлюються всі раніше відмальовані дані.
Не дуже ефективно, чи не правда? Особливо якщо елементів в колекції буде більше.
Зазначу, що тут я кажу про рядкові шаблонизаторы, які на вхід отримують шаблон і дані, а на виході видають рядок з HTML–кодом, яку і потрібно вставити в DOM.
Але рядкові шаблонизаторы вони на те і рядкові, що поняття не мають про HTML і DOM. Їм все одно які дані в який шаблон вставляти і що потім розробник буде з цими даними робити.

Про розумні шаблонизаторы

На противагу звичайним, строковим, шаблонизаторам є більш розумні.
Їх перевага в тому, що вони–то вже «в курсі», що працюють з HTML і на виході віддають не рядок з HTML–кодом, а DOM–дерево підперті у нього даними. При цьому, кожен елемент даних, який надійшов на вхід такого шаблонизатора, стає пов'язаним з відповідним йому DOM–вузлом. Таким чином, при зміні якого–небудь елемента колекції, відбувається оновлення тільки того вузла, з яким цей елемент пов'язаний. Повертаємося до прикладу зі списком з 100 елементів. У випадку з розумним шаблонизатором, він сам визначить, вміст яких елементів колекції було змінено і оновить тільки відповідні вузли, не перерисовывая при цьому всі уявлення.
Такий підхід, безсумнівно, більш ефективний, особливо при великих наборах даних.
Але, знову ж таки, навіть він не вирішує головної проблеми – роботи з даними.
Так, подібні шаблонизаторы вбудована можливість використовувати pipe–фільтри, які дозволяють модифікувати дані перед висновком, але, по–перше: такий підхід погіршує читаність шаблону; по–друге: це можливість шаблонизатора, а не фреймворку, який використовує шаблонизатор і в більш складних ситуаціях не врятує від нагромадження коду, як зі сторони шаблону, так і з боку контролера/компонента.
Як наслідок, виникає фундаментальна проблема, яка вже не раз тут згадувалася – обробка даних.

Про basis.js

Basis.js – це фреймворк, ядро якого будувалося з розрахунком на роботу з даними.
Відмінною особливістю фреймворку є сама модель побудови програми.
Закінчену додаток на basis.js являє собою ієрархію компонентів, між якими циркулює потік даних.
При цьому, образ потоку даних найкраще відображає суть того, що відбувається, адже створення програми на basis.js – це налагодження зв'язків між частинами програми.
Грамотно побудована програма позбавляє від необхідності навіть банального перебору DOM–елементів.
За все відповідає сам фреймворк і його гнучкі інструменти.
При цьому, сам фреймворк побудований на абстракціях, що дозволяє в повній мірі використовувати переваги поліморфізму і, в більшості випадків, абстрагуватися від конкретної реалізації.

Основи роботи з basis.js ви можете почерпнути цієї статті, написаної автором фреймворка.
Мені випала можливість продовжити цикл статей.

Від теорії – до практики

Однією з головних складових будь-якого SPA є реакція програми на дії користувача, іншими словами – своєчасне оновлення даних.
Якщо ми говоримо про дані, то в basis.js є безліч абстракцій для опису даних.
Найбільш простий з них є клас Token.
Token дозволяє описати скалярне значення, на зміну якого можна передплатити:
let token = new basis.Token(); // створюємо Token
let fn = (value) => console.log('значення змінено на:', value); // функція–обробник

token.attach(fn); // підписуємося на зміну значення

token.set('привіт'); // встановлюємо нове значення
// console> пункт змінено на: привіт

token.set('habrahabr'); // console> пункт змінено на: habrahabr
token.set('habrahabr'); // нічого не виведе в консоль, т. к. значення не відрізняється від вже встановленого

token.detach(fn); // повідписуємося від змін значення

token.set('basis.js'); // нове значення буде встановлено, але у токен вже немає передплатників

Метод Token#attach – додає передплатника на зміни значення токена.
Метод Token#detach – видаляє раніше доданого передплатника.

Більш того, один маркер може залежати від іншого:
let token = new basis.Token();
let sqr = token.as((value) => value * value); // створюємо ще один маркер, залежний від token
let fn = (value) => console.log('значення змінено на:', value);

token.attach(fn);

token.set(4); // console> пункт змінено на: 4
console.log(token.get()); // console> 4
console.log(sqr.get()); // console> 16

token.set(8); // console> пункт змінено на: 8
console.log(token.get()); // console> 8
console.log(sqr.get()); // console> 64

token.detach(fn);

token.set(10);
console.log(token.get()); // console> 10
console.log(sqr.get()); // console> 100

Token#as – створює новий токен і автоматично підписує його на зміни значення оригінального сертифіката.
Змінюючи значення оригінального сертифіката, воно передається функції, зазначеної у as і породжений токен записується її результат.
Таким чином, можна створити ланцюжок токенів, значення кожного з яких будуть залежати від значення попереднього сертифіката:
let token = new basis.Token();
let sqr = token.as((value) => value * value);
let twoSqr = sqr.as((value) => value * 2);
let fn = (value) => console.log('значення змінено на:', value);

token.attach(fn);

token.set(4); // console> пункт змінено на: 4
console.log(token.get()); // console> 4
console.log(sqr.get()); // console> 16
console.log(twoSqr.get()); // console> 32

token.detach(fn);

token.set(10);
console.log(token.get()); // console> 10
console.log(sqr.get()); // console> 100
console.log(twoSqr.get()); // console> 200

Токен може бути зруйнований викликом методу Token#destroy.
Зруйнований токен стає абсолютно марний, тому як перестає повідомляти абонентів про оновлення свого значення, так і додати нових передплатників до нього вже не можна.

Ось такий простий і зручний механізм оновлення даних закладений в basis.js.

Давайте подивимося як використовуються токени в компонентах basis.js:
let Node = require('basis.ui').Node;
let nameToken = new basis.Token(");

new Node({
container: document.body, // де розмістити елемент
template: resource('./template.tmpl'), // шаблон
binding: {
name: nameToken
},
action: { // обробник подій
input: (e) => nameToken.set(e.sender.value)
}
});

А ось і шаблон:
<div>
<input type="text" event-input="input">
<div>Привіт {name}</div>
</div>

Тепер, при введенні даних в текстове поле, замість {name} буде підставлятися актуальне значення.
Іншими словами: властивість Node#binding представляє собою об'єкт, властивості якого можуть бути токени.
Node підписується на зміни значення таких токенів і своєчасно оновлює подання, при чому тільки ті його частини, які реально змінилися.

Звичайно ж не можу обійти увагою приклад з Token#as:
let Node = require('basis.ui').Node;
let nameToken = new basis.Token(");

new Node({
container: document.body,
template: resource('./template.tmpl'),
binding: {
name: nameToken.as(value => value.toUpperCase())
},
action: {
input: (e) => nameToken.set(e.sender.value)
}
});

Вже здогадалися що буде виведено?

Звичайно, ви можете заперечити, мовляв:
пппфффф… ангуляре те ж саме робиться взагалі без єдиної рядки коду
Так, але пізніше ви побачите як елегантно basis.js справляється з набагато більш складними завданнями.

Не дивлячись на те, що в цій частині нашого циклу було більше теорії, ніж практики, ми розглянули один з найважливіших аспектів basis.js, який допоможе нам у розумінні подальших тем.

Спасибі за увагу!

P.s.: якщо ви, як і я, любите ES6 і хочете використовувати його разом з basis.js, тоді вам знадобиться ось цей плагін.
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.