Хотите узнать Vue.js с нуля? Получите полную коллекцию книг Vue, охватывающих основы, проекты, советы и инструменты и многое другое с SitePoint Premium. Присоединяйтесь сейчас всего за $ 14,99 / месяц .
Я готов поспорить, что есть много разработчиков, которые все еще тянутся к jQuery, когда им поручено создавать простые приложения. Часто нам нужно добавить интерактивность на страницу, но поиск JavaScript-инфраструктуры кажется излишним — со всеми дополнительными килобайтами, шаблонами, инструментами сборки и компоновщиками модулей. Включение jQuery из CDN кажется легким делом.
В этой статье я хотел бы попытаться убедить вас, что использование Vue.js (далее — Vue), даже для относительно простых проектов, не должно быть головной болью и поможет вам написать лучше код быстрее. Мы возьмем простой пример, кодируем его в jQuery, а затем воссоздаем его в Vue шаг за шагом.
Что мы строим
В этой статье мы собираемся создать базовый онлайн-счет с использованием этого шаблона с открытым исходным кодом от Sparksuite . Надеюсь, это должно внести освежающие изменения в еще один список дел и обеспечить достаточную сложность, чтобы продемонстрировать преимущества использования чего-то вроде Vue, в то же время оставаясь простым для понимания.
Мы собираемся сделать это интерактивным, предоставив входные данные для товара, цены за единицу и количества, а также автоматически пересчитав столбец Цена при изменении одного из значений. Мы также добавим кнопку для вставки новых пустых строк в счет-фактуру и поле Итого, которое будет автоматически обновляться при редактировании данных.
Я изменил шаблон так, чтобы HTML-код для одной (пустой) строки теперь выглядел так:
<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.
$('table').on('mouseup keyup', 'input[type=number]', calculateTotals);
Мы присоединяем слушателя к самой таблице, которая будет выполнять функцию calcTotals при изменении значений « Стоимость единицы» или « Количество» :
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)); }
Эта функция ищет все строки элементов в таблице и проходит по ним, передавая каждую строку в функцию calcSubtotal, а затем суммируя результаты. Эта total
затем вставляется в соответствующее место в счете.
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>
в строке и умножаем второе и третье вместе, чтобы получить промежуточный итог. Это значение затем вставляется в последнюю ячейку в строке.
function formatAsCurrency(amount) { return `$${Number(amount).toFixed(2)}`; }
У нас также есть небольшая вспомогательная функция, которую мы используем, чтобы убедиться, что как промежуточные итоги, так и итоги отформатированы с двумя десятичными разрядами и имеют префикс с символом валюты.
$('.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(); });
.$('.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(); });
Наконец, у нас есть обработчик кликов для нашей кнопки « Добавить строку» . Здесь мы выбираем последнюю строку элемента и создаем дубликат. Входные данные клонированной строки устанавливаются в значения по умолчанию и вставляются как новая последняя строка. Мы также можем быть добрыми к нашим пользователям и установить фокус на первый ввод, готовый к тому, чтобы они начали печатать.
Вот завершенная демонстрация jQuery:
Downsides
Так что не так с этим кодом в его нынешнем виде, или, скорее, что может быть лучше?
Возможно, вы слышали, что некоторые из этих более новых библиотек, такие как Vue и React, утверждают, что они декларативные, а не обязательные. Конечно, глядя на этот код jQuery, большинство из них читается как список инструкций о том, как манипулировать DOM. Цель каждого раздела кода — «что» — часто трудно понять с помощью деталей того, «как» это делается. Конечно, мы можем прояснить цель кода, разбив его на хорошо именованные функции, но этот код все еще будет требовать некоторых усилий для умственного анализа, если вы вернетесь к нему через некоторое время.
Другая проблема с таким кодом заключается в том, что мы сохраняем состояние нашего приложения в самом DOM. Информация о заказанных товарах существует только как часть HTML, составляющего пользовательский интерфейс. Это может показаться не большой проблемой, когда мы отображаем информацию только в одном месте, но как только нам нужно отобразить одни и те же данные в нескольких местах в нашем приложении, становится все сложнее гарантировать, что каждый фрагмент синхронизирован. Там нет единого источника правды.
Хотя ничто в jQuery не мешает нам сохранять наше состояние вне DOM и избегать этих проблем, библиотеки, такие как Vue, предоставляют функциональность и структуру, которые облегчают создание хорошей архитектуры и написание более чистого, более модульного кода.
Преобразование в Vue
Итак, как бы мы воссоздали эту функциональность, используя Vue?
Как я упоминал ранее, Vue не требует от нас использования модуля-компоновщика, или транспилятора, или выбора их отдельных файловых компонентов (файлов .vue
) для начала работы. Как и jQuery, мы можем просто включить библиотеку из CDN. Давайте начнем с замены тега script:
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.js"></script>
Следующее, что нам нужно сделать, это создать новый экземпляр Vue:
const app = new Vue({ el: 'table' });
Единственная опция, которую мы должны предоставить здесь — это el
, который является селектором (как мы бы использовали с jQuery), определяющим, какой частью документа мы хотим управлять Vue.
Мы можем назначить Vue ответственным за что угодно со всей страницы (например, для одностраничного приложения) или одного <div>
. Для нашего примера счета мы дадим Vue контроль над таблицей HTML.
Данные
Давайте также добавим данные для трех строк примера в наш экземпляр Vue:
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
:
<tr class="item" v-for="item in items"> </tr>
Vue будет повторять эту разметку для каждого элемента массива (или объекта), который вы передадите в конструкцию v-for
, что позволит вам ссылаться на каждый элемент внутри цикла — в данном случае, как item
. Поскольку Vue наблюдает за всеми свойствами объекта data
, он будет динамически повторно отображать разметку при изменении содержимого items
. Все, что нам нужно сделать, это добавить или удалить элементы в наше состояние приложения, и Vue позаботится об обновлении интерфейса.
Нам также нужно добавить <input>
для пользователя, чтобы заполнить описание, цену за единицу и количество товара:
<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
:
const app = new Vue({ // ... methods: { myMethod() {} }, // ... })
Давайте определим метод addRow
который мы можем вызвать, чтобы добавить новый элемент в наш массив items
:
methods: { addRow() { this.items.push({ description: '', quantity: 1, price: 0 }); }, },
Обратите внимание, что любые методы, которые мы создаем, автоматически привязываются к самому экземпляру Vue, поэтому мы можем получить доступ к свойствам из нашего объекта data
и других методов, как свойств this
.
Итак, теперь, когда у нас есть наш метод, как мы его называем при нажатии кнопки Добавить строку ? Синтаксис для добавления прослушивателей событий к элементу в шаблоне v-on:event-name
:
<button class="btn-add-row" @click="addRow">Add row</button>
Vue также предоставляет нам ярлык, чтобы мы могли использовать @
вместо v-on:
как я показал в коде выше. Для обработчика мы можем указать любой метод из нашего экземпляра Vue.
Вычисленные свойства
Теперь все, что нам нужно сделать, это отобразить итоговую сумму внизу счета. Потенциально мы могли бы сделать это в самом шаблоне: как я упоминал ранее, Vue позволяет нам помещать любые операторы JavaScript в фигурные скобки. Тем не менее, гораздо лучше хранить в наших шаблонах нечто большее, чем простую логику; проще и проще проверить, если мы будем хранить эту логику отдельно.
Мы могли бы использовать другой метод для этого, но я думаю, что вычисляемое свойство лучше подходит. Подобно созданию методов, мы передаем нашему экземпляру Vue computed
объект, содержащий функции, результаты которых мы хотим использовать в нашем шаблоне:
const app = new Vue({ // ... computed: { total() { return this.items.reduce((acc, item) => acc + (item.price * item.quantity), 0); } } });
Теперь мы можем ссылаться на это вычисленное свойство в нашем шаблоне:
<tr class="total"> <td colspan="3"></td> <td>Total: ${{ total }}</td> </tr>
Как вы, возможно, уже заметили, вычисленные свойства могут рассматриваться как данные; нам не нужно называть их скобками. Но использование вычисляемых свойств имеет еще одно преимущество: Vue достаточно умен, чтобы кэшировать возвращаемое значение и переоценивать функцию, только если одно из свойств данных зависит от изменений.
Если бы мы использовали метод для суммирования итоговой суммы, вычисление выполнялось бы каждый раз при повторной визуализации шаблона. Поскольку мы используем вычисляемое свойство, общая сумма пересчитывается только при изменении одного из полей quantity
или price
.
фильтры
Вы могли заметить, что у нас есть небольшая ошибка в нашей реализации. В то время как удельные затраты являются целыми числами, наша общая сумма и промежуточные итоги отображаются без центов. Мы действительно хотим, чтобы эти цифры всегда отображались с точностью до двух знаков после запятой.
Вместо того, чтобы изменять как код, который вычисляет промежуточные итоги, так и код, который вычисляет общий итог, Vue предоставляет нам хороший способ справиться с общими задачами форматирования, такими как: фильтры.
Как вы уже могли догадаться, чтобы создать фильтр, мы просто передаем объект с этим ключом нашему экземпляру Vue:
const app = new Vue({ // ... filters: { currency(value) { return value.toFixed(2); } } });
Здесь мы создали очень простой фильтр под названием currency
, который вызывает toFixed(2)
для полученного значения и возвращает результат. Мы можем применить его к любому выводу в нашем шаблоне следующим образом:
<td>Total: ${{ total | currency }}</td>
Вот законченное демо Vue:
Подводя итоги
Сравнивая две версии кода бок о бок, в приложении Vue выделяются несколько моментов:
- Четкое разделение между пользовательским интерфейсом и логикой / данными, которые его управляют : код гораздо легче понять и легче тестировать
- Интерфейс пользователя декларативный : вам нужно только заботиться о том, что вы хотите увидеть, а не о том, как манипулировать DOM для его достижения.
Размер (в КБ) обеих библиотек практически одинаков. Конечно, вы можете немного уменьшить JQuery с помощью пользовательской сборки, но даже с относительно простым проектом, таким как пример нашего счета, я думаю, что простота разработки и читаемость кода оправдывают разницу.
Vue также может сделать намного больше, чем мы рассмотрели здесь. Его сила заключается в том, что вы можете создавать модульные, многократно используемые компоненты пользовательского интерфейса, которые можно объединить в сложные интерфейсные приложения. Если вы заинтересованы в углублении в Vue, я бы порекомендовал проверить, как начать работу с Vue.js 2.0 Framework .