Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!
Используете для создания приложений Vue, React, Angular или Svelte? Я использую, и если вы тоже, и уверен, что вам уже давно не приходилось писать приложение, которое выводит информацию без этих прекрасных инструментов.
Когда-то многие из нас писали веб-приложения только с помощью тех средств, что были встроены в браузер. И хотя современные инструменты помогают нам абстрагироваться от этого (и имеют много других преимуществ), всё ещё полезно знать, что происходит у них под капотом.
При выводе небольшого количества информации вам может потребоваться использовать HTML, JavaScript и DOM без каких-либо инструментов. Недавно я написал несколько базовых примеров, которые иллюстрируют основы веб-разработки и помогают в изучении DOM, HTML, JavaScript и принципов работы браузера. Этот опыт позволил мне понять, что другие разработчики — возможно, вы, — будут рады вспомнить, как выводить информацию без использования библиотек.
Кроме того, это забавно, полезно и помогает понять ценность современных библиотек и фреймворков, которые делают за нас так много работы.
Давайте рассмотрим разные способы вывода информации. И держите под рукой эту документацию!
Образец приложения
Вот приложение, которое я продемонстрировал в своей статье. Оно извлекает список персонажей и выводит их при нажатии на кнопку. Также приложение отображает индикатор выполнения при извлечении списка.
Инструменты
Важно выбрать правильные инструменты. В этом упражнении нам нужно отобразить в браузере информацию с помощью конкретных инструментов, доступных всем нам. Никаких фреймворков. Никаких библиотек. Никаких проблем.
Мы будем использовать только HTML, TypeScript/JavaScript, CSS и браузерную DOM (объектную модель документа). Давайте посмотрим, как можно отобразить HTML.
Здесь вы можете почитать о том, как писать на TypeScript с помощью VS Code.
Как вы могли заметить, я использую TypeScript. Особенности его набора прекрасно помогают избегать багов, и я пользуюсь его возможностью транспилирования в любой формат JavaScript. Если хотите, можете использовать чистый JavaScript. В будущем я напишу статью о том, как транспилировать TypeScript.
Подход
Наше приложение отображает индикатор выполнения и список персонажей. Начнём с самого простого — индикатора, — и рассмотрим разные способы его отображения. Затем перейдём к списку и разберёмся, как меняется ситуация, если нужно выводить больше информации.
Отображение индикатора выполнения
Индикатор должен отображаться, пока приложение решает, какие персонажи должны попасть в список. То есть сначала индикатор невидим, затем пользователь нажимает кнопку обновления, индикатор появляется, и снова исчезает после отображения списка.
Все нижеописанные методики требуют размещения элементов по мере их создания. Например, индикатор и список персонажей где-то должны располагаться. Одним из решений является ссылка на существующий элемент, уже содержащий их. Или можно ссылаться на элемент и заменять его новыми данными.
Отображение индикатора с помощью внутреннего HTML
Создать HTML и поместить его внутрь другого элемента — это самое старое решение, но оно работает! Выбираете элемент, в котором появится индикатор, а затем задаёте для новых данных свойство
innerHtml
. Обратите внимание, что мы также пытаемся сделать код удобочитаемым с помощью шаблонных строк, позволяющих охватывать несколько строк.const heroPlaceholder = document.querySelector('.hero-list');
heroPlaceholder.innerHTML = `
<progress
class="hero-list progress is-medium is-info" max="100"
></progress>
`;
Просто. Легко. Почему так нельзя сказать про любой код? Ну посмотрите, как быстро этот фрагмент решает проблему!
Вероятно, вы уже заметили, насколько уязвим этот код. Одна ошибка в HTML — и всё! У нас проблема. И действительно ли он удобочитаем? Да, это так, но что будет, когда HTML станет слишком сложным? Когда у вас 20 строк кода с классами, атрибутами и значениями… ну, вы поняли. Да, решение не идеальное. Но при небольшом количестве HTML вполне работает, и я рекомендую присмотреться к нему любителям всё писать в одну строку.
Также обратите внимание, как встроенный контент может влиять на удобочитаемость и стабильность кода. Например, если вам нужно добавить информацию внутрь индикатора для изменяющегося сообщения о загрузке, то можно сделать это с помощью замены, например,
${message}
. Это не хорошо и не плохо, просто добавляет удобство при создании больших шаблонных строк.И последнее:
innerHTML
может влиять на скорость отображения. Я не увлекаюсь избыточной оптимизацией, но лучше проверяйте производительность, потому что innerHTML
может быть причиной возникновения циклов отрисовки и вывода макетов в браузере. Имейте в виду.Индикатор выполнения с помощью DOM API
Уменьшить громоздкость длинных строк можно с помощью DOM API. Создаём элемент, добавляем необходимые классы и атрибуты, задаём значения, а потом добавляем его в DOM.
const heroPlaceholder = document.querySelector('.hero-list');
const progressEl = document.createElement('progress');
progressEl.classList.add('hero-list', 'progress', 'is-medium', 'is-info');
const maxAttr = document.createAttribute('max');
maxAttr.value = '100';
progressEl.setAttributeNode(maxAttr);
heroPlaceholder.replaceWith(progressEl);
Преимущество в том, что нужно писать больше кода и полагаться на API. То есть по сравнению с
innerHTML
при опечатках повышается вероятность, что система сообщит об ошибке, которая поможет найти причину проблемы и придумать решение.А недостаток в том, что требуется шесть строк кода для вывода информации, которая выводится одной строкой с помощью
innerHTML
.Код DOM API для отображения индикатора более читабелен, чем код с
innerHTML
? Я бы поспорил. А не в том ли причина, что HTML-код индикатора очень короткий и простой? Возможно. Если бы было 20 строк, то innerHTML
стал бы гораздо сложнее в чтении… впрочем, и код DOM API тоже.Отрисовка индикатора с помощью шаблонов
Ещё один способ заключается в создании тега
<tеmplate>
и использовании его для упрощения вывода информации.Создадим
<tеmplate>
и присвоим ему ID. Шаблон не будет отображён на HTML-странице, но позднее вы можете ссылаться на его содержимое и использовать его. Это очень полезно, вы можете писать HTML везде, где это целесообразно: на HTML-странице, с применением всех полезных возможностей HTML-редактора.<template id="progress-template">
<progress class="hero-list progress is-medium is-info" max="100"></progress>
</template>
Затем код может взять шаблон с помощью метода
document.importNode()
из DOM API. По мере необходимости код может манипулировать содержимым шаблона. Теперь добавьте это содержимое в DOM, чтобы отобразить индикатор.const heroPlaceholder = document.querySelector('.hero-list');
const template = document.getElementById('progress-template') as HTMLTemplateElement;
const fetchingNode = document.importNode(template.content, true);
heroPlaceholder.replaceWith(fetchingNode);
Шаблоны — хороший способ сборки HTML; мне он нравится, потому что позволяет писать HTML там, где это имеет смысл, и можно меньше делать с помощью кода на TypeScript/JavaScript.
Можно ли импортировать шаблоны из других файлов? Да, но с помощью других библиотек. Однако сейчас мы от них сознательно отказались. Импорт HTML обсуждается годами, но, как видно из материалов сайта «Can I Use», даже не все современные браузеры поддерживают эту возможность.
Отображение списка персонажей
Теперь поговорим о том, как можно отображать список персонажей с помощью тех же трёх методик. Отличие от отрисовки одиночного HTML-элемента
<prоgress>
и списка персонажей заключается в том, что теперь мы:- отображаем несколько HTML-элементов;
- добавляем несколько классов;
- добавляем определённую последовательность из дочерних элементов;
- отображаем для каждого персонажа много одинаковой информации;
- динамически отображаем текст внутри элементов.
Отображение персонажей с помощью внутреннего HTML
При использовании
innerHTML
целесообразно начать с массива персонажей и итерировать его. Таким образом можно создавать по строке за раз. Строки будут отличаться только именами персонажей и описанием, которые можно вставлять с помощью шаблонных строк. Каждый персонаж в массиве создаёт тег li
, который связывается со строчным массивом. Наконец, строчный массив преобразуется в чистый HTML, обёрнутый в ul
и назначенный для innerHTML
.function createListWithInnerHTML(heroes: Hero[]) {
const rows = heroes.map(hero => {
return `<li>
<div class="card">
<div class="card-content">
<div class="content">
<div class="name">${hero.name}</div>
<div class="description">${hero.description}</div>
</div>
</div>
</div>
</li>`;
});
const html = `<ul>${rows.join()}</ul>`;
heroPlaceholder.innerHTML = html;
Это работает. И возможно, это более удобочитаемо. Что насчёт производительности? Трудно ли выявить (или вообще возможно?) опечатки при наборе кода? Судить вам. Но давайте не будем делать выводов, пока не разберёмся с другими методиками.
Отображение персонажей с помощью DOM API
function createListWithDOMAPI(heroes: Hero[]) {
const ul = document.createElement('ul');
ul.classList.add('list', 'hero-list');
heroes.forEach(hero => {
const li = document.createElement('li');
const card = document.createElement('div');
card.classList.add('card');
li.appendChild(card);
const cardContent = document.createElement('div');
cardContent.classList.add('card-content');
card.appendChild(cardContent);
const content = document.createElement('div');
content.classList.add('content');
cardContent.appendChild(content);
const name = document.createElement('div');
name.classList.add('name');
name.textContent = hero.name;
cardContent.appendChild(name);
const description = document.createElement('div');
description.classList.add('description');
description.textContent = hero.description;
cardContent.appendChild(description);
ul.appendChild(li);
});
heroPlaceholder.replaceWith(ul);
}
Отображение персонажей с помощью шаблонов
Список персонажей можно отобразить с помощью шаблонов. Для этого применяется та же методика, что и для отображения элемента
<prоgress>
. Сначала на HTML-странице создаём шаблон. Этот HTML-код будет чуть сложнее, чем в случае с <prоgress>
. Но это не проблема. Это просто HTML внутри HTML-страницы, так что мы можем легко исправлять ошибки и форматирование с помощью прекрасного редактора VS Code.<template id="hero-template">
<li>
<div class="card">
<div class="card-content">
<div class="content">
<div class="name"></div>
<div class="description"></div>
</div>
</div>
</div>
</li>
</template>
Теперь можете написать код для создания списка персонажей. Сначала создаём
ul
для обёртывания строк с персонажами li
. Теперь можно проходить по массиву и с помощью того же метода document.importNode()
применять один и тот же шаблон для каждого персонажа. Обратите внимание, что шаблон можно многократно использовать для создания каждой строки. Он превращается в чертёж-исходник, по которому создаётся необходимое количество строк.Список персонажей должен содержать имена и описания. То есть нужно будет заменять конкретные значения внутри шаблона. Для этого целесообразно каким-то образом определять и ссылаться на места, куда вы будете вставлять эти значения. В нашем примере для получения ссылки на каждого персонажа используется метод
querySelector('your-selector')
, после чего задаётся имя и описание.function createListWithTemplate(heroes: Hero[]) {
const ul = document.createElement('ul');
ul.classList.add('list', 'hero-list');
const template = document.getElementById('hero-template') as HTMLTemplateElement;
heroes.forEach((hero: Hero) => {
const heroCard = document.importNode(template.content, true);
heroCard.querySelector('.description').textContent = hero.description;
heroCard.querySelector('.name').textContent = hero.name;
ul.appendChild(heroCard);
});
heroPlaceholder.replaceWith(ul);
}
Этот шаблон проще других методик? Я считаю, что относительно проще. С моей точки зрения, код построен по шаблону, который упрощает воспроизведение и читабельность, а также лучше защищает от ошибок.
Итоги
Я не упомянул о том, как отображают информацию популярные фреймворки и библиотеки. Vue, React, Angular и Svelte существенно облегчают задачу и требуют писать меньше кода. Также у них есть и другие преимущества. В этой статье мы рассмотрели лишь относительно простые методики вывода информации с помощью DOM и чистого HTML, а также TypeScript/JavaScript.
К чему мы пришли?
Надеюсь, вы теперь представляете, как выводить информацию без использования библиотек. Есть ли другие способы? Конечно. Можно написать функции, которые упростят код и позволят использовать его многократно? Конечно. Но однажды вам захочется поэкспериментировать с одним из таких прекрасных инструментов, как Vue, React, Angular или Svelte.
Эти фреймворки делают за нас много работы. Вы можете помнить, как выводить информацию с помощью чистого DOM-кода с JavaScript или jQuery. Или как делать это с помощью инструментов вроде handlebars или mustache. А может быть, вы никогда не выводили информацию в DOM без помощи фреймворка. Можете сами представить, какие процессы протекают под капотом современных библиотек и фреймворков, когда они выводят для вас информацию на экран.
Полезно знать, что можно сделать с помощью чистого HTML, TypeScript/JavaScript и CSS, даже если вы используете фреймворк, который избавляет вас от всего этого.
Вне зависимости от вашего опыта, я надеюсь, что вам был полезен этот краткий экскурс в некоторые методики отображения информации.