От автора: сегодня у нас противостояние Vue vs jQuery. Я готов поспорить, что есть много разработчиков, которые все еще склонны использовать jQuery, когда им поручено создавать простые приложения. Часто бывает, что нам нужно добавить интерактивность на страницу, но поиск JavaScript-фреймворка кажется излишним — все эти дополнительные килобайты, шаблоны, инструменты сборки и компоновщики модулей. Включение jQuery из CDN кажется простым решением.
В этой статье я хотел бы попытаться убедить вас, что использование Vue.js (далее — Vue), даже для относительно простых проектов, не должно доставить вам головную боль, и это поможет быстрее написать лучший код. Мы возьмем простой пример, закодируем его в jQuery, а затем шаг за шагом воссоздадим его во Vue.
Что мы создаем
В этой статье мы собираемся создать простой онлайн-счет с использованием этого шаблона с открытым исходным кодом от Sparksuite. Надеюсь, это должно обеспечить достаточную сложность, чтобы продемонстрировать преимущества использования чего-то вроде Vue, в то же время оставаясь простым для понимания.
Мы собираемся сделать это интерактивным, реализовав возможность предоставить входные данные о товаре, цене за единицу и количестве, а также сделав автоматический пересчет столбца «Price» при изменении одного из значений. Мы также добавим кнопку для вставки новых пустых строк в счет-фактуру и поле «Total», которое будет автоматически обновляться при редактировании данных.
Я изменил шаблон так, чтобы HTML-код для одной (пустой) строки теперь выглядел так:
1 2 3 4 5 6 |
<tr class="item"> <td><input value="" /></td> <td>$<input type="number" value="0" /></td> <td><input type="number" value="1" /></td> <td>$0.00</td> </tr> |
jQuery
Итак, прежде всего, давайте посмотрим, как мы можем сделать это с помощью jQuery.
1 |
$('table').on('mouseup keyup', 'input[type=number]', calculateTotals); |
Мы добавляем прослушиватель к самой таблице, который будет выполнять функцию calculateTotals при изменении значений «Unit Cost» или «Quantity»:
1 2 3 4 5 |
function calculateTotals() { const subtotals = $('.item').map((idx, val) => calculateSubtotal(val)).get(); const total = subtotals.reduce((a, v) => a + Number(v), 0); $('.total td:eq(1)').text(formatAsCurrency(total)); } |
Эта функция находит все строки элементов в таблице и перебирает их, передавая каждую строку функции calculateSubtotal, а затем суммируя результаты. Затем total вставляется в соответствующее место в счете.
1 2 3 4 5 6 7 8 9 |
function calculateSubtotal(row) { const $row = $(row); const inputs = $row.find('input'); const subtotal = inputs[1].value * inputs[2].value; $row.find('td:last').text(formatAsCurrency(subtotal)); return subtotal; } |
В приведенном выше коде мы собираем ссылки на все input в строке и умножаем второе и третье значения, чтобы получить промежуточный итог. Это значение затем вставляется в последнюю ячейку в строке.
1 2 3 |
function formatAsCurrency(amount) { return `$${Number(amount).toFixed(2)}`; } |
У нас также есть небольшая вспомогательная функция, которая обеспечивает, чтобы промежуточные суммы и общий итог были отформатированы с двумя десятичными разрядами, а перед числом указывался знак валюты.
1 2 3 4 5 6 7 8 9 10 |
$('.btn-add-row').on('click', () => { const $lastRow = $('.item:last'); const $newRow = $lastRow.clone(); $newRow.find('input').val(''); $newRow.find('td:last').text('$0.00'); $newRow.insertAfter($lastRow); $newRow.find('input:first').focus(); }); |
Наконец, у нас есть обработчик кликов для кнопки «Add row» . Здесь мы выбираем последнюю строку элемента и создаем дубликат. На входе для клонированной строки установлены значения по умолчанию, и она вставляется как новая последняя строка. Мы также можем позаботиться о наших пользователях и установить фокус на первый элемент ввода, чтобы они могли начать вводить данные. Вот окончательная демо-версия jQuery:
Так что же не так с этим кодом в его нынешнем виде, или, скорее, что мы можем улучшить?
Возможно, вы слышали, что некоторые из более новых библиотек, таких как Vue и React, утверждают, что они декларативные, а не императивные. Конечно, когда мы посмотрим на этот код jQuery, мы увидим, что большинство из них читается как список инструкций относительно того, как манипулировать DOM. Цель каждого раздела кода — «что» — часто трудно понять с помощью деталей того, «как» это делается. Конечно, мы можем прояснить цель кода, разбив его на хорошо именованные функции, но этот код все еще будет требовать некоторых усилий для умственного анализа, если вы вернетесь к нему через некоторое время.
Другая проблема с таким кодом заключается в том, что мы сохраняем состояние приложения в самом DOM. Информация о заказанных товарах существует только как часть HTML, составляющего пользовательский интерфейс. Это может показаться не большой проблемой, когда мы отображаем информацию только в одном месте, но как только нам нужно отобразить одни и те же данные в нескольких местах в приложении, становится все сложнее гарантировать, что каждый фрагмент синхронизирован. Нет единого источника.
Хотя ничто в jQuery не мешает нам сохранять состояние вне DOM и решить эти проблемы, библиотеки, такие как Vue, предоставляют функционал и структуру, которые облегчают создание оптимальной архитектуры и написание более чистого, более модульного кода.
Преобразование во Vue
Итак, как бы мы могли воссоздать эту функцию с помощью Vue? Как я уже упоминал ранее, Vue не требует для начала работы использования модуля-компоновщика или транспилятора или выбора их отдельных файловых компонентов (.vue файлов). Как и jQuery, мы можем просто включить библиотеку из CDN. Давайте начнем с изменения тега script:
1 |
<script src="//cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.js"></script> |
Следующее, что нам нужно сделать, это создать новый экземпляр Vue:
1 2 3 |
const app = new Vue({ el: 'table' }); |
Единственная опция, которую мы должны предоставить здесь — el, это селектор (который мы бы использовали и с jQuery), определяющий, какой частью документа мы хотим управлять через Vue.
Мы можем назначить для Vue управлять чем угодно — от всей страницы (например, для одностраничного приложения) до отдельного div. Для нашего примера счета мы передадим Vue контроль над HTML-таблицей.
Данные
Давайте также добавим в экземпляр Vue данные для трех строк примера:
1 2 3 4 5 6 7 8 9 10 |
const app = new Vue({ el: 'table', data: { items: [ { description: 'Website design', quantity: 1, price: 300 }, { description: 'Hosting (3 months)', quantity: 1, price: 75 }, { description: 'Domain name (1 year)', quantity: 1, price: 10 }, ] } }); |
Свойство data — это место, где мы храним состояние нашего приложения. Это включает в себя не только данные, для приложения, но также информацию о состоянии пользовательского интерфейса (например, какой раздел в настоящее время активен в группе вкладок, или о раскрытии или закрытии частей аккордеона).
Vue призывает хранить состояние приложения отдельно от его представления (то есть DOM) и централизованно в одном месте — единственном источнике.
Изменение шаблона
Теперь давайте настроим шаблон для отображения элементов из нашего объекта данных. Так как мы сказали Vue, что хотим, чтобы он управлял таблицей, мы можем использовать синтаксис его шаблона в HTML, чтобы сообщить Vue, как ее визуализировать и управлять нею.
Используя атрибут v-for, мы можем визуализировать блок HTML для каждого элемента в нашем массиве items:
1 2 3 |
<tr class="item" v-for="item in items"> </tr> |
Vue будет повторять эту разметку для каждого элемента массива (или объекта), который вы передадите в конструкцию v-for, что позволит вам ссылаться на каждый элемент внутри цикла — в данном случае как item. Поскольку Vue наблюдает за всеми свойствами объекта data, он будет динамически повторно отображать разметку при изменении содержимого items. Все, что нам нужно сделать, это добавить или удалить элементы из состояние приложения, и Vue позаботится об обновлении интерфейса.
Нам также нужно добавить элементы input, через которые пользователи будут вводить описание, цену за единицу и количество товара:
1 2 3 4 |
<td><input v-model="item.description" /></td> <td>$<input type="number" v-model="item.price" /></td> <td><input type="number" v-model="item.quantity" /></td> <td>${{ item.price * item.quantity }}</td> |
Здесь мы используем атрибут v-model, чтобы установить двустороннюю привязку между входными данными и свойствами в модели данных. Это означает, что любое изменение входных данных приведет к обновлению соответствующих свойств в модели элемента, и наоборот.
В последней ячейке мы используем двойные фигурные скобки {{ }} для вывода текста. Мы можем использовать в фигурных скобках любое допустимое выражение JavaScript, поэтому мы умножаем два наших свойства элемента и выводим результат. Опять же, поскольку Vue наблюдает за моделью данных, изменение любого свойства вызовет автоматическую переоценку выражения.
События и методы
Теперь у нас есть настроенный шаблон для рендеринга набора items, но как нам добавить новые строки? Поскольку Vue будет отображать все, что находится в items, для отображения пустой строки нам просто нужно поместить объект с любыми нужными значениями по умолчанию в массив items.
Чтобы создать функции, к которым мы можем получить доступ из шаблона, нам нужно передать их экземпляру Vue в качестве свойств объекта methods:
1 2 3 4 5 6 7 |
const app = new Vue({ // ... methods: { myMethod() {} }, // ... }) |
Давайте определим метод addRow, который мы можем вызвать, чтобы добавить новый элемент в массив items:
1 2 3 4 5 |
methods: { addRow() { this.items.push({ description: '', quantity: 1, price: 0 }); }, }, |
Обратите внимание, что любые методы, которые мы создаем, автоматически привязываются к самому экземпляру Vue, поэтому мы можем получить доступ к свойствам объекта data и другим методам, как к свойствам this.
Итак, теперь, когда у нас есть наш метод, как мы его будем вызывать при нажатии кнопки «Add row»? Синтаксис для добавления прослушивателей событий к элементу в шаблоне — v-on:event-name:
1 |
<button class="btn-add-row" @click="addRow">Add row</button> |
Vue также предоставляет нам ярлык, мы можем использовать @ вместо v-on:, как я показал в коде выше. Для обработчика мы можем указать любой метод из экземпляра Vue.
Вычисляемые свойства
Теперь все, что нам нужно сделать, это отобразить итоговую сумму внизу счета. Потенциально мы могли бы сделать это в самом шаблоне: как я упоминал ранее, Vue позволяет помещать любые операторы JavaScript в фигурные скобки. Тем не менее, гораздо лучше хранить в шаблонах нечто большее, чем простую логику; будет проще, если мы будем хранить эту логику отдельно.
Мы могли бы использовать другой метод для этого, но я думаю, что вычисляемое свойство подходит лучше. Подобно созданию методов, мы передаем экземпляру Vue объект computed, содержащий функции, результаты которых мы хотим использовать в нашем шаблоне:
1 2 3 4 5 6 7 8 |
const app = new Vue({ // ... computed: { total() { return this.items.reduce((acc, item) => acc + (item.price * item.quantity), 0); } } }); |
Теперь мы можем ссылаться на это вычисляемое свойство в шаблоне:
1 2 3 4 |
<tr class="total"> <td colspan="3"></td> <td>Total: ${{ total }}</td> </tr> |
Как вы, возможно, уже заметили, вычисляемые свойства могут рассматриваться как данные; нам не нужно вызывать их в скобках. Но использование вычисляемых свойств имеет еще одно преимущество: Vue достаточно умен, чтобы кэшировать возвращаемое значение и переоценивать функцию, только если одно из свойств данных зависит от изменений.
Если бы мы использовали метод для суммирования итогового результата, вычисление выполнялось бы каждый раз при повторной визуализации шаблона. Поскольку мы используем вычисляемое свойство, общая сумма пересчитывается только при изменении одного из элементов полей quantity или price.
Фильтры
Вы могли заметить, что у нас есть небольшая ошибка в реализации. Цена единицы является целым числом, и общая и промежуточные суммы отображаются без центов. На самом же деле нам нужно, чтобы эти цифры всегда отображались с точностью до двух знаков после запятой.
Вместо того, чтобы изменять как код, который вычисляет промежуточные суммы, так и код, который вычисляет общий результат, Vue предоставляет нам хороший способ справиться с общими задачами форматирования — фильтры. Как вы уже могли догадаться, для создания фильтра мы просто передаем объект с этим ключом нашему экземпляру Vue:
1 2 3 4 5 6 7 8 |
const app = new Vue({ // ... filters: { currency(value) { return value.toFixed(2); } } }); |
Здесь мы создали очень простой фильтр с именем currency, который вызывает toFixed(2) для значения, которое он принимает, и возвращает результат. Мы можем применить его к любому выводу в шаблоне следующим образом:
1 |
<td>Total: ${{ total | currency }}</td> |
Вот окончательная демо-версия Vue:
Подводя итоги
Сравнивая две версии, в приложении Vue выделяются несколько моментов:
Четкое разделение между пользовательским интерфейсом и логикой / данными, которые его управляют: код гораздо легче понять и легче тестировать
Пользовательский интерфейс является декларативным: вам нужно только заботиться о том, что вы хотите видеть, а не о том, как манипулировать DOM для достижения этого.
Размер (в КБ) обеих библиотек практически одинаков. Конечно, вы можете немного уменьшить JQuery с помощью пользовательской сборки, но даже для относительно простого проекта, такого как пример нашего счета, я думаю, что простота разработки и читаемость кода оправдывают разницу.
Vue также может сделать гораздо больше, чем мы рассмотрели. Его сила заключается в том, что у вас есть возможность создавать модульные, многократно используемые компоненты пользовательского интерфейса, которые можно объединить в сложные интерфейсные приложения.
Автор: Nilson Jacques
Источник: //www.sitepoint.com
Редакция: Команда webformyself.