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, когда пользователь добавит больше.


Мы могли бы также использовать тег <script type = "text/template"> в качестве шаблона строки и добавить один по умолчанию в DOMContentLoaded, это подход который мы будем использовать.


jQuery HTML Template

Использование шаблона больше соответствует тому, как мы могли бы создать компонент в Vue. Вот как выглядит разметка HTML:


<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>jQuery Checkout UI</title>
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/css/bootstrap.min.css">
    <style type="text/css">
        body {
            margin: 3em
        }

        button {
            cursor: pointer;
        }

        .unit-price {
            margin-right: 2rem;
            color: #999;
        }
    </style>
</head>
<body>
    <div class="container" id="app">
        <form>
            <!-- A placeholder for the list of attendee inputs -->
            <div class="attendee-list"></div>
            <div class="row justify-content-center">
                <div class="col-sm-6"></div>
                <div class="col-sm-2">
                    <button type="button" class="btn btn-secondary add-attendee">Add Attendee</button>
                </div>
            </div>
            <hr>
            <div class="row justify-content-center">
                <div class="col-sm-6">
                    <!-- A placeholder for the unit price -->
                    <span id="unit-price" class="unit-price"></span>
                </div>
                <div class="col-sm-2 text-left">
                    <button type="submit" id="checkout-button" class="btn btn-primary">
                        Pay
                        <!-- A placeholder for the checkout total -->
                        <span class="amount"></span></button>
                </div>
            </div>
        </form>
    </div>

    <script type="text/template" data-template="attendee">
        <div class="attendee row justify-content-center">
            <div class="col-sm-3">
                <div class="form-group">
                <label class="sr-only">Name</label>
                <input 
                    class="form-control"
                    placeholder="Enter name"
                    name="attendees[][name]"
                    required>
                </div>
            </div>
            <div class="col-sm-3">
                <div class="form-group">
                <label class="sr-only">Email address</label>
                <input
                    type="email"
                    class="form-control"
                    placeholder="Enter email"
                    name="attendees[][email]"
                    required>
                </div>
            </div>
            <div class="col-sm-2 text-left">
                <button type="button" class="btn btn-light remove-attendee">
                    <span aria-hidden="true">×</span> Remove
                </button>
            </div>
        </div>
    </script>

    <script src="https://code.jquery.com/jquery-3.2.1.slim.min.js"></script>
    <script src="app.js"></script>
</body>
</html>

Мы используем бета-версию Bootstrap 4 для макета. Мы определили несколько заполнителей jQuery, которые будут заполнены данными в $(document).ready(), но из разметки трудно сказать что произойдет. Вам нужно будет смотреть HTML и JavaScript одновременно, чтобы понять смысл предполагаемой функциональности. Возвращение к этому проекту через несколько месяцев потребует приличного количества умственных усилий, чтобы выяснить что происходит.


В нашем файле app.js мы будем заполнять цену за один билет и общую цену, которая будет отображаться с помощью JavaScript на кнопке проверки. Каждый раз, когда пользователь нажимает кнопку «Add Attendee», мы добавим новую строку в контейнер <div class="attendee-list"></div> из шаблона.


Чтобы заполнить список участников повторяющимися полями формы, мы используем тег <script> в качестве клиентского шаблона. Браузеры будут игнорировать сценарий из-за type="text/template", что означает, что он не будет выполнен.


Рядом с закрывающим тегом используем последнюю версию 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 части этого руководства:


<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Vue Checkout UI</title>
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/css/bootstrap.min.css">
    <style type="text/css">
        body {
            margin: 3em
        }

        button {
            cursor: pointer;
        }

        .unit-price {
            margin-right: 2rem;
            color: #999;
        }
    </style>
</head>
<body>
  <div class="container" id="app">
    <form>
      <div
        class="row justify-content-center"
        v-for="(attendee, index) in attendees"
        :key="index"
      >
        <div class="col-sm-3">
          <div class="form-group">
            <label class="sr-only">Name</label>
            <input
              class="form-control"
              aria-describedby="emailHelp"
              placeholder="Enter name"
              v-model="attendee.name"
              name="attendees[][name]"
              required
            >
          </div>
        </div>
        <div class="col-sm-3">
          <div class="form-group">
            <label class="sr-only">Email address</label>
            <input
              type="email"
              class="form-control"
              placeholder="Enter email"
              v-model="attendee.email"
              name="attendees[][email]"
              required
            >
          </div>
        </div>
        <div class="col-sm-2 text-left">
          <button type="button" class="btn btn-light">
            <span aria-hidden="true">×</span> Remove</button>
        </div>
      </div>
      <div class="row justify-content-center">
        <div class="col-sm-6"></div>
        <div class="col-sm-2">
          <button type="button" class="btn btn-secondary">Add Attendee</button>
        </div>
      </div>
      <hr>
      <div class="row justify-content-center">
        <div class="col-sm-6">
          <span class="unit-price">${{ cost }} ea.</span>
        </div>
        <div class="col-sm-2 text-left">
          <button type="submit" class="btn btn-primary">Pay</button>
        </div>
      </div>
    <form>
  </div>
  <script src="https://unpkg.com/vue@2.4.4/dist/vue.js"></script>
  <script src="app.js"></script>
</body>
</html>

Разметка очень похожа на нашу jQuery версию, но, возможно, вы заметили переменную для цены одного элемента:


<span class="unit-price">${{ cost }} ea.</span>

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:


<button
  type="submit"
  class="btn btn-primary"
>
  Pay ${{ cost * attendees.length }}
</button>

Тем не менее, слишком много логики в ваших шаблонах делает их менее выразительными и сложными в поддержке. Вместо этого мы можем использовать вычисляемые свойства:


(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, которое использует первое вычисляемое свойство для умножения количества элементов на стоимость.


Теперь мы можем обновить кнопку проверки используя вычисляемое свойство. Обратите внимание как в результате описано имя вычисленного свойства:


<button
  type="submit"
  class="btn btn-primary"
>
  Pay ${{ checkoutTotal }}
</button>

Если вы обновите свой браузер, вы должны увидеть на кнопке итоговую сумму, вычисленную автоматически.


Когда вы добавляете участника, вычисляемое свойство автоматически обновляется и отображается в DOM.


Добавление участнииков с Vue

Мы готовы посмотреть, как мы добавляли бы участников, используя события Vue.


В jQuery мы использовали обработчик событий DOM:


$('.add-attendee').on('click', function () {});

В Vue мы подключаем событие в шаблоне. На мой взгляд это упрощает чтение HTML, потому что у нас есть выразительный способ узнать какие события связаны с данным элементом.


Вы можете использовать v-on:click="addAttendee":


<!-- Using v-on: -->
<button
  type="button"
  class="btn btn-secondary"
  v-on:click="attendees.push({ name: '', email: ''})"
>
  Add Attendee
</button>

Или сокращённый вариант click=”addAttendee”:


<!-- Using @click -->
<button
  type="button"
  class="btn btn-secondary"
  @click="attendees.push({ name: '', email: ''})"
>
  Add Attendee
</button>

Нормально использовать любой стиль, но хорошая практика придерживаться одного метода. Я предпочитаю сокращённый стиль.


Когда кнопка нажата, мы помещаем новый объект в массив attendees в шаблоне. Я хотел показать вам этот стиль, чтобы вы могли понять, что вы можете просто запустить JavaScript в атрибуте.


В большинстве случаев лучше использовать обработчики событий, потому что, как правило, события имеют более сложную логику, связанную с ними:


<button
  type="button"
  class="btn btn-secondary"
  @click="addAttendee"
>
  Add Attendee
</button>

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 предоставляет модификаторы событий, используемые с точкой (.) как часть атрибута:


<button
  type="button"
  class="btn btn-secondary"
  @click.prevent="addAttendee"
>
  Add Attendee
</button>

Ваши методы ориентированы на данные, а Vue автоматически обрабатывает события DOM с помощью модификаторов событий.


Удаление участников с Vue

Удаление участников похоже на их добавление, но вместо добавления объекта к массиву нам нужно удалить его на основе индекса массива в другом обработчике событий:


<button
  type="button"
  class="btn btn-light"
  @click.prevent="removeAttendee(index)"
>
  <span aria-hidden="true">×</span>
  Remove
</button>

Мы используем индекс массива чтобы ссылаться на нужного посетителя, которого хотим удалить. Если вы вспомните в нашем цикле v-for, мы определили индекс:


<div
  class="row justify-content-center"
  v-for="(attendee, index) in attendees"
  :key="index"
>
  <!-- Attendee inputs -->
</div>

Внутри экземпляра Vue мы определяем метод removeAttendee, который использует splice для удаления одного элемента из массива на основе индекса:


methods: {
  removeAttendee: function (index) {
    this.attendees.splice(index, 1);
  },
  // ...
}

С помощью обработчика removeAttendee вы можете добавлять и удалять участников!


Мы также хотим соответствовать бизнес-требованиям c отображением кнопки «Remove» только при добавлении нескольких участников. Мы не хотим разрешать пользователю удалять всех участников.


Мы можем сделать это со встроенной условной директивой v-show:


<button
  type="button"
  class="btn btn-light"
  @click.prevent="removeAttendee(index)"
  v-show="quantity > 1"
>
  <span aria-hidden="true">×</span>
  Remove
</button>

Мы использовали вычисляемое свойство 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 мне показался довольно простым и понравился синтаксис, а увидев эту статью решил поделиться ей с сообществом (ну и заодно попробовать себя в переводах).


Я постарался сохранить стилистику автора, но некоторые предложения чуть переделал для того, чтобы правильнее передать смысл.


Сильно не ругайте — это мой первый перевод.

Поделиться публикацией

Комментарии 26

    +6
    Синтаксис «Усы», наверное стоило перевести как синтаксис библиотеки «Mustache» ={
      0
      Handlebars
        0
        А разве он не появился раньше в Django (а, может быть, ещё раньше в Ruby On Rails, с которым я не знаком)?
        +5
        Почему всегда сравнивают jquery лапшу (без отделения бизнес логики от представления) и фреймворки? Можно так же элегантно писать использую jquery. Просто нужно вынести data за описание интерфейса. Создать какой-нибудь объект, который будет отвечать за бизнес логику и подписать на изменения его данных. Тут идея в другом, jquery это императивный подход описания интерфейса, а все фреймворки в основном используют декларативный подход
          +2
          На мой взгляд статья выглядит не сравненим фреймворков как таковых, а попыткой показать что как раз декларативный подход (просто автор предпочитает vue) понятнее чем императивный (на примере jquery)
            0
            Ну так декларативный на то и декларативный. И тут уже не важно какой фреймворк выбирать.
            0

            так покажите свой пример кода, думаю, тут всем интересно будет

              0
              можете глянуть ниже. (не туда ответил)
            +3
            var eventMixin = {
            	on: function (eventName, handler) {},
            	off: function (eventName, handler) {},
            	trigger: function (eventName) {}
            };
            
            //конструктор (модель)
            var Book      = function () {};
            Book.byObject = function () {};
            
            var bookService = (new function () {
            	var self = this,
            	   books = [];
            	//добавляем примесь событий сюда
            	
            	
            	self.add = function (data) {
            		var newBook = Book.byObject(data);
            		books.push(newBook);
            		self.trigger('add', newBook);
            	};
            	//... и т.д. CRUD
            	//так же сюда все xhr.
            });
            
            //далее view
            var BookForm = function ($container) {
            	this.onClickSubmit = function (cb) {
            		$($container).on('click', '.js-submit', function () {
            			cb(formData) //тут можно прокинуть все поля формы
            		});
            	};
            };
            
            //непосредственно инит приложения
            $(function () {
            	bookService.on('add', function () {
            		//Вызвать какое-либо изменения интерфейса
            		//Не обязательно BookForm, а любого другого.
            	});
            	
            	var bookForm = new BookForm(/**/);
            	bookForm.onClickSubmit(function (formData) {
            		bookService.add(formData);
            	})
            });
            

            Как-то так. Основная идея, что представление и бизнес логика очень слабо связаны. Другой вопрос в том, удобно ли так описывать интерфейсы.
              +3

              Вместо того, чтобы городить свой собственный eventMixin лучше взять существующий Backbone.


              И про такой подход обычно так и говорят — "мы пишем на Backbone", потому что в нем основная логика и крутится.


              А когда говорят "мы пишем на jQuery", это означает, что никакой дополнительной библиотеки не используется, а код выглядит примерно как и есть в статье.

              0
              Т.е. если бекенд не на JS, нужно писать шаблоны для Vue, которые будут отдаваться «как есть» и Vue будет их уже рендерить, а бекенд будет все отдавать в виде JSON, т.е. что типа API, верно?
                0
                Да, шаблоны по сути — html. А на клиенте будут запрашиваться данные с сервера в виде JSON, что бы эти шаблоны и наполнять
                  +1
                  А потом приходит сеошник и хочет странного. Например, чтоб роботы текст на странице видели.
                    +1

                    А зачем роботу видеть текст в непредназначенном для поисковиков (а возможно — и интернета) приложении?

                    0

                    Правильно ли я понял, что для данной задачи может быть полезен ssr, или это не решает задачи первичного наполнения данными?

                  0
                  Чтобы заполнить список участников повторяющимися полями формы, мы используем тег
                  Рядом с закрывающим тегом

                  кажется, что-то пропало…
                    +1
                    Видел эту статью на Vue-newsletter, по части перевода хочу отметить переводчику — не стоит гнаться за буквальным переводом фраз.
                    По сути — перевод весьма неплох. Но, в исходнике напихано достаточно немало устойчивых выражений, которые в русском языке не прозвучат как следует. К примеру, я бы не писал так: «чтобы в кнопке она была правильной», я бы конкретизировал — «чтобы текст в кнопке был правильным». Ну и т.д., а за статью — благодарю, актуальненько.
                    –3
                    Если честно, то чудовищный синтаксис у этого Vue. Использование кучи хитроумных кастомных атрибутов в тегах — это современный подход?
                    В этом отношении jquery на порядок чище и читаемее.
                      0
                      Надеюсь это шутка
                        +1
                        Синтаксис становится читаемее там где пользуются компонентами.Здесь показана как ни парадоксально, именно «код-лапша» на Vue.js. Примитивизм использования допустим только для хелло-ворлда в одну строку. А вот там где нужна запутанная логика — синтаксис уже не решает.Замучаетесь отслеживать кто кого откуда и когда вызывает и что меняет.Попробуйте написать панельку оператора который чз виджет отслеживает посетителей сайта и ведет с ними диалоги и принимает звонки.На ангуляр 1 молиться начнете как нефиг делать.
                          0
                          Ангуляр1 немного щупал. Как-то приятнее.
                            0
                            Тоже на уровне ХеллоВорлд щупали? Да многие ведутся на магию Ангуляра, а потом в реале начинают писать чудо-контроллеры в овер9000 строк.Так что не забудьте пощупать
                            вглубь до уровня разнесения логики по сервисам, с этого уровня Ангуляр собственно и начинается
                        0
                        Вызов функции syncRemoveButtons() в addAttendee() в примере с jQuery вообще лишний.
                          +1
                          Во-первых, спасибо за перевод, наконец-то все прояснилось. А то уже который год читаю про ангуляры-реакты, и везде авторы позиционируют их только как средство для написания SPA, а тут оказывается вполне себе можно заюзать Vue на пацанском сайте с постбэками(=

                          Перед тем, как полезу глубже, хотелось бы осознать еще пару моментов:

                          1. Vue полностью оберегает нас от прямых манипуляций с DOM? Все работает через мониторинг и реагирование на изменение данных? Если так, то есть ли все-таки возможность обращаться к элементам через какой то движок типа Sizzle, или все-таки надо использовать jquery, если не привык к vanillajs? Чтобы, например, проинитить data из какого то элемента.
                          2. Насколько гибки Vue-шные атрибуты, отвечающие за работу с DOM? Например, есть ли возможность тому же v-show указать эффект вроде v-show.slide или fade, задать параметры? Или есть ли возможость навесить обработчики не напрямую элементу, а потомкам через предка, как это делает on у jquery?
                          3. Если хочется оживить несколько разных областей на странице при помощи Vue, как надо действовать согласно best practices: создавать один Vue на страницу с «под-data-ми» или несколько на каждую область (тогда наверно и с событиями будет меньше путаницы)?
                            0
                            1)А вы ЛЕЗЬТЕ ГЛУБЖЕ) ТАМ и осознаете. А иначе так и будете о каждой теме читать по нескольку лет, а потом очевидные моменты в ней узнавать как откровение.
                            2)Получать что то по селектору для обслуживания логики приложения обычно считается wrong-way при работе с фреймворками.От так то, благородный дон)
                              0

                              Здесь очень важно понять саму концепцию. Vue и другие фреймворки предлагают подход "создаем DOM из данных" в отличие от jQuery, где "извлекаем данные из DOM". Поэтому возможности обхода по селекторам крайне ограничены. Если нужно получить DOM-ноду — есть ref.


                              Если планировать использовать Vue, то нужно перестать кодировать данные в data-атрибутах. Здесь показывается, какие есть у Vue альтернативные способы передать данные в компонент.

                            Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                            Самое читаемое