Усталені практики при роботі з шаблонами проектування JavaScript
() translation by (you can also view the original English article)
Можливо, шаблони не знадобляться вам при розробці простих веб-додатків, проте ці додатки не повинні бути особливо складними, щоб використання шаблонів ставало доречним. Так само як і при роботі з будь-яким іншим інструментом або технологією, є декілька усталених практик, про які вам варто пам'ятати при використанні шаблонів. Ми розглянемо у цьому посібнику деякі з них.
1. Використовуйте Underscore для простих додатків, а Handlebars – для складних
Якщо вам потрібно щось особливе, то чи можна мені порекомендувати вам Handlebars?
Перш за все, якщо ви тільки не Джон Резіг, то, скоріше за все, підшукуєте надійну бібліотеку для створення шаблонів. Хоча є велика кількість варіантів, є два дійсно хороших, вибір яких визначається складністю вашого проекту.
Якщо ваш проект відносно простий, то ви можете використовувати Underscore.js. Головним чином ця бібліотека пропонує допоміжні методи, корисні, якщо ви кодуєте згідно з функціональним програмуванням (* стиль програмування, згідно з яким всі конструкції мови програмування реалізовано у вигляді функцій), проте у дійсності у ній є метод _.template
, який максимально полегшує використання шаблонів. За налаштуванням у ній використовуються розділювачі у стилі ERB (* реалізація eRuby (Embedded Ruby) – шаблонізатора, за допомогою якого можливо використання Ruby у контексті текстового документу; це мова для написання шаблонів, в основі якої лежить Ruby) – <%= %>
, проте це можна запросто змінити. Перевага цього варіанту полягає у тому, що у будь-якому проекті, де потрібно використовувати шаблони, скоріше за все, вже буде додано бібліотеку Underscore через очевидну користь цієї бібліотеки у більшості випадків. Для того щоб ознайомитися з подробицями про Underscore, звертайтеся до написаного Siddharth вичерпного посібника, розташованого прямо тут на Nettuts+.
Якщо вам потрібно щось особливе, то чи можна мені порекомендувати вам Handlebars? Надаючи можливість використання безлічі блокових виразів (* хелперів) (на зразок #each
, використовуваного для додання циклу, та #if
, застосовуваного в якості умовного оператора), реєстрації ваших власних хелперів, Handlebars забезпечить вас усім потрібним для створення навіть найскладнішого шаблону.
Якщо ви не знайомі з Handlebars, то ознайомтеся з цим посібником, написаним Gabriel Manricks.
Зрозуміло, що є й інші бібліотеки для створення шаблонів окрім розглянутих мною тут; сміло можете з ними ознайомитися! Я порекомендував тільки ці дві, оскільки вони мені подобаються та я і сам їх використовую. Їх також найчастіше рекомендують у товаристві програмістів.
2. Створюйте хелпери для шаблонів
Більшість бібліотек для створення шаблонів будуть використовувати об'єкт з даними, передаваний вами до функції, що відповідає шаблону, в якості контексту.
Будуть моменти, коли передавані вами до шаблону дані не будуть відформатовані, як вам потрібно. У цих випадках вам потрібно буде створити свої власні функції для форматування даних. Якщо ви використовуєте щось на зразок Handlebars, то ви запросто можете зареєструвати хелпер; проте інші варіанти подібних бібліотек на зразок Underscore не надають подібної можливості. Вам потрібно буде релалізувати її самостійно.
Причиною, через яку це не так легко виконати, як може здатися на перший погляд, полягає у тому, що більшість бібліотек для створення шаблонів буде використовувати об'єкт з даними, передаваними вами до функції, що відповідає шаблону, в якості контексту (значення this
). Тому ваш хелпер повинен бути частиною цього об'єкта. Є декілька способів реалізувати це. Найпростіший спосіб виконується завдяки додаванню функції до об'єкта з даними перед передачею його до функції, що відповідає шаблону. Нижче наведено приклад:
1 |
// assume data object and template function
|
2 |
|
3 |
data.formatPrice = function (priceInCents) { |
4 |
return "$" + (priceInCents / 100).toFixed(2); |
5 |
}
|
6 |
|
7 |
var html = template(data); |
Цей варіант підходить для одиничного випадку, проте можливо, що у вас буде безліч наборів шаблонів, для кожного з яких потрібен свій власний набір хелперів. У цих випадках мені подобається обгортати функцію, що відповідає шаблону, в іншу функцію, за допомогою якої хелпери будуть додані до об'єкта data
:
1 |
var productTemplate = function (data) { |
2 |
var template = _.template("the template string"); |
3 |
data.helperFunction1 = function () { return "whatever" }; |
4 |
return template(data); |
5 |
};
|
Є декілька способів вдосконалення цього підходу (ви могли би почати з отримання «сирої» функції, створеної в результаті компіляції шаблону, за межами розглядуваної вище, можливо, за допомогою замикання (* комбінація функції та лексичного оточення, в якому її було об'явлено)), проте я дав загальне уявлення. Тепер ви можете просто передати ваші дані до тієї функції productTemplate
та скористатися вашими хелперами.
3. Зберігайте шаблони в їх власному файлі
Є декілька альтернативних варіантів, які могли би бути більш вдалими, особливо у складніших сценаріях.
Всі шаблони JavaScript, зрозуміло, спочатку являють собою текст. Найпопулярнішим (та природним) місцем для їх зберігання є ваш HTML-документ – звичайно у тегу script
з альтернативним значенням властивості type
(* наприклад text/x-handlebars-template у випадку використання Handlebars), завдяки чому браузер не намагається виконати їх. Доволі просто скористатися атрибутом innerHTML
тегу script
для отримання шаблону та передати його значення (* вміст) до функції для компіляції шаблону до відповідної функції, коли ви готові.
Проте, це не єдиний спосіб для зберігання шаблонів; на практиці це може бути не найоптимальніший варіант. Є декілька альтернативних варіантів, які могли би бути більш вдалими, особливо у складніших сценаріях.
Один з них – зберігання всіх шаблонів у файлі JavaScript. Зрозуміло, це означає, що ваші шаблони будуть зберігатися в якості рядків, а не у вигляді легшого для читання HTML-коду, але зачекайте! По-перше, ви не зобов'язані використовувати для шаблонів довше одного рядка (що справедливо для більшості шаблонів) єдиний громіздкий обгортаючий рядок. Замість цього спробуйте зробити приблизно так:
1 |
Templates = {}; |
2 |
|
3 |
Templates.contactEntry = [ |
4 |
"<h1> {{fullName}} </h1>", |
5 |
"<ul>", |
6 |
"<li> Email: {{email}} </li>", |
7 |
"<li> Phone: {{tel}} </li>", |
8 |
"</ul>" |
9 |
].join("\n"); |
Завдяки зберіганню шаблону у масиві на зразок цього з ним набагато легше працювати. За допомогою цього синтаксису ви можете з легкістю зберігати всі ваші шаблони в їх власному файлі JavaScript та гарантувати, що його буде завантажено до DOM (* Document Object Model – об'єктна модель документа) до того, як шаблони будуть потрібні. І, зрозуміло, нема потреби у зберіганні всіх шаблонів в єдиному об'єкті Template
, проте завдяки цьому ваш код стане більш впорядкованим. Той об'єкт Templates
міг би бути навіть властивістю вашого глобального об'єкта (наприклад так MyApp.Templates
).
Але зачекайте, краще попереду (як говорять). Ви можете скомпілювати всі ваші шаблони до їх відповідні функції в одному циклі:
1 |
for (var tmpl in Templates) { |
2 |
if (Templates.hasOwnProperty(tmpl) { |
3 |
Templates[t] = _.template(Templates[t]); // Underscore example |
4 |
}
|
5 |
}
|
Якщо ви використовуєте AMD (Asynchronous Module Definition (* асинхронне визначення модулів)) у вашому додатку, то цей підхід як і раніше спрацює; просто помістіть розглянутий вище код до модулю з шаблонами, що повертає той об'єкт Templates
. Проте для багатьох платформ з підтримкою AMD є плаґін для роботи з текстом, що дозволяє вам завантажувати файли з чистим текстом; замість того звичайного модуля ви отримаєте рядок. Якщо ви використовуєте бібліотеку RequireJS, то вам потрібно буде розмістити плаґін text.js
у той самій папці, де знаходиться файл require.js
. Далі ви можете зробити щось на зразок наступного:
1 |
require(["text!templates/document.html"], function (documentTemplate) { |
2 |
|
3 |
}); |
Той параметр documentTemplate
буде рядком, що містить контент, яким би він не був, того файлу templates/document.html
. При цьому підході ви не зможете розмістити безліч шаблонів в одному файлі, за виключенням того випадку, коли вирішите попрацювати з тим рядком.
4. Виконуйте попередню компіляцію ваших шаблонів
Якщо ви користуєтеся при створення додатків Rails Asset Pipeline, то можете скористатися Sprockets для попередньої компіляції ваших функцій, отриманих у результаті компіляції шаблонів.
Якщо ви задумаєтесь на секундочку, то зрозумієте, що браузер виконує деяку додаткову роботу кожного разу, коли ви створюєте шаблон. Звичайно цей шаблон початково являє собою рядок, який ви передаєте до функції для компіляції шаблонів до відповідної функції. Та функція повертає іншу функцію, в яку ви можете передавати дані та отримати HTML-код. Вищезгадана додаткова робота виконується при отриманні функції, що відповідає шаблону; ніщо не заважає виконати цей етап до того, як код JavaScript відправляється браузеру. Краще, щоб ця робота виконувалася при збірці проекту, а також мініфікації коду CSS та конкатенації вашого коду JavaScript.
Нажаль, виконати попередню компіляцію не настільки просто, як мініфікацію або конкатенацію... принаймні поки що, напевно, через наявність безлічі способів створення шаблонів. Якщо ви користуєтеся Grunt або Yeoman, то можете підшукати плаґін (на зразок цього) на веб-сайті Grunt. Якщо ви користуєтеся при створенні додатів Rails Asset Pipeline (* інструмент, що надає для фреймворка можливість конкатенації та мініфікації або стиснення ресурсів JavaScript та CSS), то можете скористатися Sprockets для попередньої компіляції ваших функцій, отриманих у результаті компіляції шаблонів.
І ще, якщо ви сміливі (та є передплатником Tuts+ рівня Premium), то можете ознайомитися із заняттям, де я виконую попередню компіляцію шаблонів з нуля у моєму курсі «Advanced Backbone Patterns and Techniques».
5. Не виконуйте обчислень у шаблонах
Не виконуйте обчислень у шаблонах.
Нещодавно при вивченні матеріалу для мого наступного проекту я натрапив на одну цікаву ідею, що стосується шаблонів JavaScript, у чудовій книзі "Recipes with Backbone". Після виходу книги вона стала усталеною практикою у товаристві розробників. Згідно з нею не варто виконувати обчислень у шаблонах. Звісно ж, інтерполяція (* відшукання проміжних значень будь-якої величини за деякими відомими її значеннями) змінних, строго кажучи, є обчисленням, проте під обчисленнями тут я більше маю на увазі код логіки (* послідовність операцій, виконуваних програмно або апаратно. Відповідно говорять про програмну логіку та про апаратну логіку) додатка. Ви можете помістити будь-який код JavaScript, який захочете, всередині розділювачів, проте при цьому запросто може ускладнитися робота з кодом. Усі ми знаємо, що розташування коду HTML, CSS та JavaScript в окремих файлах вважається усталеною практикою; завдяки цьому полегшується відстеження роботи коду та виявлення помилок за потреби. Те саме справедливо і для шаблонів: вони повинні бути місцем тільки для інтерполяції змінних. Будь-яка логіка або перетворення даних повинні виконуватися за межами шаблону.
Зрозуміло, що те, наскільки ви будете дотримуватися цього правила, залежить від вас. Ви можете вирішити, що виконання циклу у вашому шаблоні цілком доречно; у вас може бути шаблон на зразок наступного:
1 |
<h1> My List </h1> |
2 |
<ul id="myList"> |
3 |
<% list.forEach(function (item) { %> |
4 |
<li> <%= item.name %> </li> |
5 |
<% }); %> |
6 |
</ul>
|
Або ж замість цього ви можете вирішити виконувати цикл за межами шаблону завдяки створенню шаблону-обгортки та подальшого перебору елементів, компіляції підшаблонів та додаванню їх до шаблону-обгортки. У вас можуть вийти два шаблони на зразок наступних:
Шаблон-обгортка:
1 |
<h1> My List </h1> |
2 |
<ul id="myList"> |
3 |
</ul>
|
Підшаблон:
1 |
<li> <%= name %> </li> |
Зрозуміло, що у випадку використання цього способу потрібно буде написати трохи більше коду, проте у результаті ви зрозумієте, що воно того варте.
Продовжуючи тему, скажу, що до усталеної практики відноситься і врахування можливостей використовуваної вами бібліотеки або фреймворка. Наприклад, я зрозумів, що при використанні з Backbone шаблонів, створених за допомогою Underscore, легше користуватися другим способом: серед надаваних Underscore мінімальних можливостей для створення шаблонів нема синтаксису для створення циклів, і метод render
Backbone добре підходить для додавання того циклу та вставлення підшаблонів. Проте при роботі з Meteor, в якому використовується шаблонізатор Handlebars, набагато легше буде додавати цикли всередині шаблонів за допомогою блокового хелпера #each
(також можна користуватися і підшаблонами, якщо хочете).
6. Прив'язуйте дані до шаблонів
За подробицями про Backbone.stickit звертайтеся до Tuts+ Premium.
Це не завжди буде можливо, проте у деяких випадках може дуже стати у пригоді додавання для шаблонів можливості автоматичного оновлення при зміні їх даних. Це не завжди буде можливо, проте у деяких випадках може дуже стати у пригоді додавання для шаблонів можливості автоматичного оновлення при зміні їх даних.
Хоча ви би й могли, напевно, реалізувати цей функціонал самостійно без особливих труднощів, знайте, що його вбудовано в усі популярні фреймворки. Наприклад, у Backbone функція шаблону initialize
може містити деякі визначені користувачем подій, наприклад так:
1 |
this.model.on('change', this.render, this); |
Завдяки цьому кожного разу при зміні значення атрибута моделі буде викликано функцію шаблону render
та шаблон буде перекомпільовано. Також ви можете використовувати плаґін на зразок backbone.stickit, який виконає прив'язування даних за вас. Якщо ви працюєте з Meteor та використовуєте один з його реактивних джерел даних (* за допомогою яких викликаються зміни), то вам буде автоматично надано можливість прив'язування даних – без потреби виконання дій з вашого боку. Я недостатньо знайомий з якимись іншими фреймворками, щоб точно пояснити, як ця можливість реалізується у них, проте будь-який фреймворк, що заслуговує на увагу, повинен надавати дещо подібне.
7. Спрощуйте ваші шаблони
Дуже швидко ваші шаблони можуть вийти з-під контролю та стати громіздкими
Якщо ви неуважні, то дуже швидко ваші шаблони можуть вийти з-під контролю та стати громіздкими. Тому завжди варто розумно обмежувати розмір ваших шаблонів. При занадто великому розмірі їх буде важче оновлювати та буде ускладнено вдале розділення коду. З іншого боку, при занадто малому розмірі вони не будуть того варті та пізніше знизять продуктивність вашого додатка.
Тому так важливо знайти золоту середину. Обходьтеся з вашими шаблонами таким же чином, як ви пишете ваш код JavaScript або CSS: оформлюйте їх у вигляді модулів. Так, кожний «кусочок» UI або віджет повинен мати свій власний шаблон, проте не забувайте і про підшаблони. Вони корисні, коли менші частини віджета містять складні макети або стани, а також коли вони мають обробники для безлічі подій, але пам'ятайте, що їх використання – палиця на два кінці. Не використовуйте їх, поки ви не маєте на те поважної причини.
8. Не використовуйте їх, якщо це не потрібно
Нарешті, пам'ятайте, що шаблони JavaScript – всього-на-всього ще один інструмент у вашому наборі розробника; і іноді він не просто не підходить для виконання задачі, що стоїть перед вами. Не використовуйте шаблони, якщо це не потрібно. Покладайтеся на себе: можливо, є й інші ситуації, коли використання шаблону недоречно.
Завершення
Що ж, вище було перелічено мої головні поради з використання шаблонів JavaScript, проте, напевно, ви можете щось додати! Якщо так, то, будь ласка, поділіться своїми рекомендаціями у розділі для додавання коментарів нижче, щоб ми могли продовжити обговорення.