[Из песочницы] Vue.js tutorial: от jQuery к Vue.js
Привет, Хабр! Представляю вашему вниманию перевод статьи Vue.js Tutorial: From jQuery to Vue.js автора Paul Redmond.
Что касается библиотек JavaScript, то никогда не было более популярной библиотеки, чем jQuery. Она создавалась для обхода DOM элементов с использованием CSS селекторов в то время, когда совместимость браузеров была важной проблемой для разработчиков.
Фактически jQuery настолько универсален, что я подумал что он отлично передаст то, почему я люблю писать UI с Vue, используя компонентный JavaScript. В этом руководстве мы сначала рассмотрим создание пользовательского интерфейса с jQuery, а затем перепишем его с помощью Vue.
Проект
Довольно типично, когда есть форма на которую требуется динамически добавить несколько полей с помощью JavaScript. Представьте, что у нас есть онлайн-форма оформления, которая позволяет пользователю приобретать несколько билетов, для которых требуется имя и адрес электронной почты для каждого билета:
Реализация этого сначала на jQuery является хорошим шагом, прежде чем мы сделаем то же самое с помощью Vue. Многие разработчики знакомы с jQuery и он обеспечивает отличный контраст с очень отличающимся подходом, который вы должны использовать для создания динамических интерфейсов.
Я создал примеры кода с использованием jQuery и с использованием Vue на Code Pen.
Версия с использованием jQuery
Есть дюжина способов которыми мы могли бы построить этот интерфейс с помощью jQuery. Например, мы могли бы создать форму с одним набором полей в HTML разметке, а затем позволить jQuery взять на себя динамическое добавление дополнительных полей в DOM, когда пользователь добавит больше.
Мы могли бы также использовать тег в качестве шаблона строки и добавить один по умолчанию в DOMContentLoaded, это подход который мы будем использовать.
jQuery HTML Template
Использование шаблона больше соответствует тому, как мы могли бы создать компонент в Vue. Вот как выглядит разметка HTML:
jQuery Checkout UI
Мы используем бета-версию Bootstrap 4 для макета. Мы определили несколько заполнителей jQuery, которые будут заполнены данными в $(document).ready (), но из разметки трудно сказать что произойдет. Вам нужно будет смотреть HTML и JavaScript одновременно, чтобы понять смысл предполагаемой функциональности. Возвращение к этому проекту через несколько месяцев потребует приличного количества умственных усилий, чтобы выяснить что происходит.
В нашем файле app.js мы будем заполнять цену за один билет и общую цену, которая будет отображаться с помощью JavaScript на кнопке проверки. Каждый раз, когда пользователь нажимает кнопку «Add Attendee», мы добавим новую строку в контейнер из шаблона.
Чтобы заполнить список участников повторяющимися полями формы, мы используем тег
Рядом с закрывающим тегом используем последнюю версию jQuery и app.js, в котором мы начнем работу с динамическими обновлениями пользовательского интерфейса.
Инициализация jQuery
Чтобы начать создавать нашу версию с jQuery, давайте инициализируем форму, вычислим общую сумму, добавим строку по умолчанию и установим цену из data:
// app.js
$(document).ready(function () {
var data = {
cost: 9.99
};
/**
* Get the attendee count
*/
function getAttendeeCount() {
return $('.attendee-list .row.attendee').length;
}
function addAttendee() {
$('.attendee-list').append(
$('script[data-template="attendee"]').text()
);
}
function syncPurchaseButton() {
// Total up the count for the checkout button total
$('#checkout-button span.amount').html(
'$' + data.cost * getAttendeeCount()
);
}
//
// Initialize the form
//
// Set up the unit cost of one ticket
$('#unit-price').html('$' + data.cost + ' ea.');
// Add one attendee by default on init
addAttendee();
syncPurchaseButton();
});
Первая часть кода устанавливает литерал объекта data, содержащий одно свойство цены. Цена — это цена одного билета. Возможно, вы захотите установить цену одного билета динамически, но для наших целей она просто захардкожена.
У нас есть несколько вспомогательных функций, включая получение количества участников с помощью DOM query. Использование DOM является единственным точным способом определения этого значения с помощью jQuery.
Вторая вспомогательная функция добавляет нового участника в список, используя шаблон в нашей разметке.
Функция syncPurchaseButton () использует getAttendeeCount () для вычисления и заполнения кнопки покупки конечной суммой.
Если вы хотите получить такую же сумму покупки в любом месте шаблона, вам нужно будет синхронизировать все экземпляры в DOM с помощью селектора классов, но в данном случае мы нацелены только на один.
Если вы загрузите страницу в этот момент, форма будет инициализирована одним посетителем, ценой одного билета и общей суммой в кнопке проверки:
Добавление участников с помощью jQuery
Затем давайте рассмотрим возможность добавления и удаления участников. jQuery имеет отличную обработку событий, включая запуск пользовательских событий. Начнем с кода, необходимого для добавления новых участников:
function addAttendee() {
$('.attendee-list').append(
$('script[data-template="attendee"]').text()
);
// Sync remove button UI
syncRemoveButtons();
}
function syncRemoveButtons() {
// If only one attendee, hide the first remove button
// otherwise, show all remove buttons
if (getAttendeeCount() === 1) {
$('.attendee-list .attendee .remove-attendee').first().hide();
} else {
$('.attendee-list .attendee .remove-attendee').show();
}
}
function syncPurchaseButton() {
// Total up the count for the checkout button total
$('#checkout-button span.amount').html(
'$' + data.cost * getAttendeeCount()
);
}
// Events
$('.add-attendee').on('click', function (event) {
event.preventDefault();
addAttendee();
$(this).trigger('attendee:add');
}).on('attendee:add', function () {
syncPurchaseButton();
syncRemoveButtons();
});
Функция syncRemoveButtons () гарантирует, что пользователь не сможет удалить поле когда оно остаётся только одно, но пользователь может удалить любую строку, если их несколько.
Теперь мы вызываем syncRemoveButtons () в функции addAttendee (), что означает, что если вы обновляете страницу, кнопка удаления скрыта, потому что количество участников — только один.
Обработчик события добавления участника вызывает функцию addAttendee (), а затем запускает пользовательское событие attendee: add.
В обработчике пользовательских событий мы синхронизируем общую цену, чтобы в кнопке она была правильной, а затем мы вызываем syncRemoveButtons (), чтобы обновить статус кнопки удаления, как описано выше.
Состояние синхронизации может выйти из-под контроля, так как ваш пользовательский интерфейс jQuery растет. Мы должны явно управлять состоянием и синхронизировать его, когда оно изменяется реагируя на события, и мы должны понимать особенности синхронизации состояний в каждом приложении.
Управление состоянием в jQuery требует дополнительных умственных усилий, потому что его можно обрабатывать и связывать с DOM различными способами. Когда состояние зависит от DOM, а не наоборот, DOM запросы для отслеживания состояния усложняются.
Удаление участников с помощью jQuery
На этом этапе, если вы обновите страницу, вы можете добавить новые строки в форму. Когда вы добавите первого дополнительного участника, кнопка удаления будет показана для каждой строки, что позволит удалить строку.
Затем давайте подключим событие удаления и убедимся, что состояние пользовательского интерфейса отображается после удаления:
// Attach an event handler to the dynamic row remove button
$('#app').on('click', '.attendee .remove-attendee', function (event) {
event.preventDefault();
var $row = $(event.target).closest('.attendee.row');
$row.remove();
$('#app').trigger('attendee:remove');
});
$('#app').on('attendee:remove', function () {
syncPurchaseButton();
syncRemoveButtons();
});
Мы добавили click event listener на идентификатор DOM #app, который позволяет нам динамически реагировать на событие click для новых строк. Внутри этого обработчика мы предотвращаем событие кнопки по умолчанию, а затем находим ближайшего предка .row в дереве DOM.
Как только найден родитель $row, мы удаляем его из DOM и запускаем пользовательское событие attendee: remove.
В обработчике события attendee: remove мы синхронизируем нашу кнопку покупки и состояние кнопки удаления.
Готовая jQuery версия
На данный момент у нас есть рабочий jQuery прототип UI нашей формы, который мы можем использовать для сравнения с Vue версией .
Вот полный файл app.js:
$(document).ready(function () {
var data = {
cost: 9.99
};
/**
* Get the attendee count
*/
function getAttendeeCount() {
return $('.attendee-list .row.attendee').length;
}
function addAttendee() {
$('.attendee-list').append(
$('script[data-template="attendee"]').text()
);
syncRemoveButtons();
}
function syncRemoveButtons() {
// If only one attendee, hide the first remove button
// otherwise, show all remove buttons
if (getAttendeeCount() === 1) {
$('.attendee-list .attendee .remove-attendee').first().hide();
} else {
$('.attendee-list .attendee .remove-attendee').show();
}
}
function syncPurchaseButton() {
// Total up the count for the checkout button total
$('#checkout-button span.amount').html(
'$' + data.cost * getAttendeeCount()
);
}
// Events
$('.add-attendee').on('click', function (event) {
event.preventDefault();
addAttendee();
$(this).trigger('attendee:add');
}).on('attendee:add', function () {
syncPurchaseButton();
syncRemoveButtons();
});
// Attach an event handler to the dynamic row remove button
$('#app').on('click', '.attendee .remove-attendee', function (event) {
event.preventDefault();
var $row = $(event.target).closest('.attendee.row');
$row.remove();
$('#app').trigger('attendee:remove');
});
$('#app').on('attendee:remove', function () {
syncPurchaseButton();
syncRemoveButtons();
});
//
// Initialize the form
//
// Set up the unit cost of one ticket
$('#unit-price').html('$' + data.cost + ' ea.');
// Add one attendee by default on init
addAttendee();
syncPurchaseButton();
});
Цель этого примера — показать вид пользовательского интерфейса, которой вы, вероятно, написали, и затем показать, как он выглядит в сравнении с Vue.js. Важным выводом здесь является состояние, привязанное непосредственно к DOM, и вы должны запросить DOM, чтобы делать вывод о состоянии.
JQuery по-прежнему позволяет писать пользовательский интерфейс, но давайте посмотрим как вы можете написать ту же функциональность используя Vue.
Введение в Vue
Большинство людей, вероятно, слышали о Vue на данный момент, но для тех кто не знаком с Vue руководство — отличное место чтобы познакомиться.
Сравнение с другими фреймворками также полезно для восприятия Vue в контрасте с другими фреймворками, с которыми вы, возможно, уже знакомы.
Я предлагаю вам установить расширение Vue devtools, доступное в Chrome и Firefox. Инструменты разработчика предоставят вам отличную отладочную информацию, когда вы изучаете и разрабатываете приложения с Vue.
Версия с использованием Vue
Наша версия Vue будет написана с использованием обычного JavaScript, чтобы избежать необходимости беспокоиться об инструментах ES6 и сосредоточиться вместо этого на примере компонента.
Вы увидите как Vue помогает отделять данные от отображения пользовательского интерфейса, с отображением данных в реактивном подходе. Нам также не нужны перемещения по DOM для вычисления значений, которые начинают казаться неуклюжими, когда вы сравниваете как это делается в jQuery по сравнению с React или Vue.
Начиная
Прежде чем писать наш шаблон и JavaScript, давайте обсудим наш подход к построению формы. Думая о данных связанных с формой, я представляю коллекцию (массив) участников и цену одного элемента.
Объект-литерал может выглядеть следующим образом:
var data = {
attendees: [
{ name: 'Example', email: 'user@example.com' }
],
cost: 9.99,
};
Если мы обновим данные, добавив еще одного участника, Vue будет слушать и готов реагировать на это изменение данных:
data.attendees.push({
name: 'Example 2',
email: 'user2@example.com'
});
Имея это в виду, давайте построим приблизительную HTML-разметку и JavaScript скелет для нашего пользовательского интерфейса.
Vue HTML шаблон
Мы будем постепенно наращивать JavaScript и HTML, чтобы пропустить вас через каждую функцию, которую мы уже рассмотрели в jQuery версии.
Вот начальная HTML разметка для Vue части этого руководства:
Vue Checkout UI
Разметка очень похожа на нашу jQuery версию, но, возможно, вы заметили переменную для цены одного элемента:
${{ cost }} ea.
Vue использует декларативный рендеринг для рендеринга данных в DOM. {{ cost }} — привязка данных с использованием синтаксиса «Усы» и символа доллара $.
Помните объект данных со свойством cost?
var data = {
cost: 9.99
};
Тег усы заменяется значением data.cost при изменении связанных данных.
Затем обратите внимание на строку v-for=»(attendee, index) in attendees», которая представляет собой цикл, перебирающий массив данных attendees, и отображающий поля ввода формы для каждого участника.
Атрибут v-for — это директива, которая «реактивно применяет к DOM изменения при обновлении значения этого выражения». В нашем примере когда массив data.attendees обновлен, DOM будет обновляться в результате действия этой директивы.
Вы должны начать видеть шаблон: мы модифицируем данные (состояние), и пользовательский интерфейс реагирует на эти изменения. В результате ваш код более декларативный и пишется легче.
Инициализация Vue
В нижней части HTML-разметки у нас есть тег, подключающий скрипт app.js с нашим Vue кодом.
Чтобы инициализировать экземпляр Vue на странице, нам нужно подключить Vue к узлу DOM. Мы предоставили контейнер div>, что означает что любая разметка внутри этого элемента DOM будет связана с Vue и реагирует на изменение данных:
(function () {
var app = new Vue({
el: '#app',
data: {
attendees: [{ name: '', email: '' }],
cost: 9.99,
},
});
})();
Мы создаем новый экземпляр Vue, связанный с элементом DOM #app и определяем основной объект данных. Объект данных включает в себя стоимость одного элемента и массив участников. Мы добавили одного пустого участника, так что наша форма по умолчанию будет отображаться с одним набором полей ввода.
Если вы удалите всех участников и сделаете их пустым массивом, вы не увидите никаких имен и сообщений электронной почты.
Все это завернуто в немедленно вызываемое функциональное выражение (IIFE — immediately-invoked function expression), чтобы исключить наш экземпляр из глобальной области.
Расчет общей цены
В jQuery версии мы рассчитали общую цену синхронизируя количество с DOM при помощи события удаления или добавления участника. В Vue, как вы могли догадаться, мы используем data, а затем представление реагирует на эти изменения автоматически.
Мы могли бы сделать что-то вроде следующего, и это будет намного лучше, чем запрос DOM:
Тем не менее, слишком много логики в ваших шаблонах делает их менее выразительными и сложными в поддержке. Вместо этого мы можем использовать вычисляемые свойства:
(function () {
var app = new Vue({
el: '#app',
data: {
attendees: [{ name: '', email: '' }],
cost: 9.99,
},
computed: {
quantity: function () {
return this.attendees.length;
},
checkoutTotal: function () {
return this.cost * this.quantity;
}
}
});
})();
Мы определили два вычисляемых свойства. Первое свойство — количество билетов, которое рассчитывается по длине массива attendees.
Второе вычисляемое свойство — checkoutTotal, которое использует первое вычисляемое свойство для умножения количества элементов на стоимость.
Теперь мы можем обновить кнопку проверки используя вычисляемое свойство. Обратите внимание как в результате описано имя вычисленного свойства:
Если вы обновите свой браузер, вы должны увидеть на кнопке итоговую сумму, вычисленную автоматически.
Когда вы добавляете участника, вычисляемое свойство автоматически обновляется и отображается в DOM.
Добавление участнииков с Vue
Мы готовы посмотреть, как мы добавляли бы участников, используя события Vue.
В jQuery мы использовали обработчик событий DOM:
$('.add-attendee').on('click', function () {});
В Vue мы подключаем событие в шаблоне. На мой взгляд это упрощает чтение HTML, потому что у нас есть выразительный способ узнать какие события связаны с данным элементом.
Вы можете использовать v-on: click=«addAttendee»:
Или сокращённый вариант click=«addAttendee»:
Нормально использовать любой стиль, но хорошая практика придерживаться одного метода. Я предпочитаю сокращённый стиль.
Когда кнопка нажата, мы помещаем новый объект в массив attendees в шаблоне. Я хотел показать вам этот стиль, чтобы вы могли понять, что вы можете просто запустить JavaScript в атрибуте.
В большинстве случаев лучше использовать обработчики событий, потому что, как правило, события имеют более сложную логику, связанную с ними:
Vue принимает свойства методов в основной объект Vue (и в компоненты), что позволяет нам определить метод обработчика событий:
(function () {
var app = new Vue({
el: '#app',
data: {
attendees: [{ name: '', email: '' }],
cost: 9.99,
},
computed: {
quantity: function () {
return this.attendees.length;
},
checkoutTotal: function () {
return this.cost * this.quantity;
}
},
methods: {
addAttendee: function (event) {
event.preventDefault();
this.attendees.push({
name: '',
email: '',
});
}
}
});
})();
Мы предотвращаем действие по умолчанию и помещаем новый объект на массив attendees. Теперь, если вы добавите участников, вы увидите новые добавленные поля ввода, а checkoutTotal будет соответствовать количеству строк:
Обратите внимание, что обработчик получает объект события, который мы можем использовать для предотвращения действия по умолчанию. Для предотвращения действие события по умолчанию или прекращение распространения (event.stopPropagation ()) Vue предоставляет модификаторы событий, используемые с точкой (.) как часть атрибута:
Ваши методы ориентированы на данные, а Vue автоматически обрабатывает события DOM с помощью модификаторов событий.
Удаление участников с Vue
Удаление участников похоже на их добавление, но вместо добавления объекта к массиву нам нужно удалить его на основе индекса массива в другом обработчике событий:
Мы используем индекс массива чтобы ссылаться на нужного посетителя, которого хотим удалить. Если вы вспомните в нашем цикле v-for, мы определили индекс:
Внутри экземпляра Vue мы определяем метод removeAttendee, который использует splice для удаления одного элемента из массива на основе индекса:
methods: {
removeAttendee: function (index) {
this.attendees.splice(index, 1);
},
// ...
}
С помощью обработчика removeAttendee вы можете добавлять и удалять участников!
Мы также хотим соответствовать бизнес-требованиям c отображением кнопки «Remove» только при добавлении нескольких участников. Мы не хотим разрешать пользователю удалять все входы.
Мы можем сделать это со встроенной условной директивой v-show:
Мы использовали вычисляемое свойство quantity чтобы показать кнопку удаления, когда количество больше единицы.
Мы могли бы также спрятать кнопку с условием v-if. Я рекомендую прочитать документацию, чтобы понять нюансы того, как они работают.
В нашем случае мы используем v-show, чтобы показать и скрыть кнопку с помощью CSS. Если вы измените код с использованием v-if и взглянете на DOM, вы увидите что Vue удаляет элемент из DOM.
Готовая Vue версия
Вот окончательная Vue версия:
(function () {
var app = new Vue({
el: '#app',
data: {
attendees: [{ name: '', email: '' }],
cost: 9.99,
},
computed: {
quantity: function () {
return this.attendees.length;
},
checkoutTotal: function () {
return this.cost * this.quantity;
}
},
methods: {
removeAttendee: function (index) {
this.attendees.splice(index, 1);
},
addAttendee: function (event) {
event.preventDefault();
this.attendees.push({
name: '',
email: '',
});
}
}
});
})();
Теперь у нас одинаковые функциональные возможности в обеих версиях! Моя цель состояла в том, чтобы проиллюстрировать переход от рабочего процесса на основе DOM к изменению данных и обновлению пользовательского интерфейса в качестве побочного эффекта этих изменений.
Разметка Vue версии более выразительна в передаче функциональности компонента, чем в jQuery версии. Невозможно определить, какие элементы будут обрабатывать события, прикрепленные в jQuery версии. Кроме того, мы не можем предвидеть, как UI будет реагировать на изменение от разметки HTML.
Что дальше?
Если у вас еще нет опыта работы с Vue, я рекомендую вам прочитать руководство в конце концов. Как и документация Laravel, руководство читается как книга. В документации вы пройдёте через все, что вам нужно, чтобы начать использовать Vue.
От переводчика:
Долгое время приходилось работать с JQuery, но познакомившись с React, а позже и с Vue, решил что надо по возможности начинать их использовать. Vue мне показался довольно простым и понравился синтаксис, а увидев эту статью решил поделиться эй с сообществом (ну и заодно попробовать себя в переводах).
Я постарался сохранить стилистику автора, но некоторые предложения чуть переделал для того, чтобы правильнее передать смысл.
Сильно не ругайте — это мой первый перевод.