Переход с jQuery на Vue

Данное руководство предназначено для тех разработчиков, которые имеют опыт работы с jQuery. Но при этом хотят поближе познакомиться с Vue.

Вспомним, для чего чаще всего используется jQuery:

  • Нахождение элементов в DOM.
  • Изменение элементов DOM.
  • Считывание и установка значений формы.
  • Валидация формы.
  • Ajax-вызов и обработка результатов;
  • Обработка событий.
  • Изменение стилей элемента.

Но давайте начнем с рассмотрения основных различий приложений, созданных с помощью этих фреймворков.

Когда лучше использовать Vue

С помощью jQuery на веб-странице можно реализовать любой функционал и в любом порядке. Например, сегодня клиент может попросить создать форму с валидацией, а через месяц разместить в заголовке сайта поисковую форму на Ajax.

Разработку проекта на Vue мы начинаем с определения «области» в DOM, с которой будем работать. Рассмотрим пример простой веб-страницы:

<body>

  <header>
    Fancy header stuff here
  </header>

  <div id="sidebar">
    Some sidebar doohicky here
  </div>

  <main>
    <p>
      Main site content here, super important stuff...
    </p>
    <div id="loginForm">
      A login form of course
    </div>
  </main>

</body>

В обычном jQuery-приложении мы можем написать код для работы с заголовком, боковой панелью, формой входа или чем-то еще.

$(document).ready(function() {

  $('header') // что-то...

  $('#sidebar') // что-то...

  $('#loginForm') // что-то... 

});

В Vue-приложении сначала нужно указать элемент, с которым будем работать. Представьте, что клиент попросил добавить валидацию к элементу loginForm. Тогда код будет следующим:

new Vue({
  el: '#loginForm',
  // Здесь код...
});

Поэтому мы добавим второе  приложение, если позже клиент решит добавить что-то в боковую панель:

new Vue({
  el:'#loginForm',
  // Здесь код...
});

new Vue({
  el:'#sideBar',
  // Здесь код...
});

Благодаря этому мы получаем преимущество инкапсуляции. Если случайно используем переменную с общим именем, это не вызовет конфликтов с другими частями кода.

Поиск в DOM

Рассмотрим конкретный пример. На странице есть кнопка, и когда она нажата, что-то происходит:

<button id="myButton">Click Me!</button>
<!-- Другие элементы... -->
<script>
$(document).ready(function() {

  $('#myButton').click(function() {
    alert(1);
  });

});
</script>

Теперь пример реализации на Vue:

<div id="app">
  <button v-on:click="doSomething">Click Me!</button>
</div>

<script>
const app = new Vue({
  el:'#app',
  methods: {
    doSomething: function() {
      alert(1);
    }
  }
});
</script>

Vue-приложение более объемно. Но обратите внимание, что разметка имеет прямую связь между действием («click») и вызываемой функцией.

Код Vue не привязан к DOM. Кроме этого не нужно сильно беспокоиться о значении идентификаторов и селекторов. Если я изменю класс или идентификатор кнопки, не нужно беспокоиться об обновлении селекторов.

Рассмотрим другой пример: поиск и изменение текста в DOM. Представьте, что кнопка при нажатии меняет текст в другой части DOM.

<button id="myButton">Click Me!</button>
<span id="result"></span>

<!-- Другие вещи... -->

<script>
$(document).ready(function() {

  $('#myButton').click(function() {
    $('#result').text('You clicked me, thanks!');
  });

});
</script>

Я добавил новый элемент span. Теперь, когда кнопка нажата, мы используем другой селектор, чтобы найти его. Для изменения текста используем jQuery.

Теперь рассмотрим Vue-версию:

<div id="app">
  <button v-on:click="doSomething">Click Me!</button>
  <!-- При клике изменяем текст в span -->
  <span>{{resultText}}</span>
</div>

<script>
const app = new Vue({
  el: '#app',
  data: {
    resultText: ''
  },
  methods: {
    doSomething: function() {
      this.resultText = 'You clicked me, thanks!';
    }
  }
});
</script>

В этом примере c помощью синтаксиса Vue мы указываем, что хотим отобразить переменную внутри элемента span.

Чтение и запись переменных формы

Рассмотрим простой jQuery- пример: чтение значений нескольких полей формы и установка значения другого:

<form>
  <input type="number" id="first"> + 
  <input type="number" id="second"> =
  <input type="number" id="sum"> 
  <button id="sumButton">Sum</button>
</form>

<script>
$(document).ready(function() {
  let $first = $('#first');
  let $second = $('#second');
  let $sum = $('#sum');
  let $button = $('#sumButton');
  
  $button.on('click', function(e) {
    e.preventDefault();
    let total = parseInt($first.val(),10) + parseInt($second.val(),10);
    $sum.val(total);
  });
});
</script>

Приведенный выше код показывает, как с помощью JQuery считывать и записывать значения полей формы, используя метод val().

В результате мы получаем из DOM четыре элемента (все три поля формы и кнопку) и используем простую математику для генерации результата.

Теперь рассмотрим Vue-версию:

<form id="myForm">
  <input type="number" v-model.number="first"> + 
  <input type="number" v-model.number="second"> =
  <input type="number" v-model="sum"> 
  <button @click.prevent="doSum">Sum</button>
</form>

<script>
new Vue({
  el: '#myForm',
  data: {
    first: 0,
    second: 0,
    sum: 0
  },
  methods: {
    doSum: function() {
      this.sum = this.first + this.second;
    }
  }
})
</script>

Здесь используется несколько сокращений Vue.  v-model - так Vue создает двустороннюю привязку между значениями в DOM и JavaScript. Переменные блока data будут автоматически синхронизироваться с полями формы. Измените данные, и форма обновится. Измените форму, и данные обновятся.

number - это флаг Vue, который указывает обрабатывать строковые значения полей формы как числа.

@click.prevent  - сначала @click определяет обработчик клика для кнопки. Затем часть .prevent блокирует поведение браузера, используемое по умолчанию при отправке формы (эквивалент event.preventDefault()).

Последняя часть - это добавление метода doSum, привязанного к кнопке. Он работает с переменными data (которые Vue делает доступными в области видимости this).

Наконец, мы можем полностью избавиться от кнопки:

<form id="myForm">
  <input type="number" v-model.number="first"> + 
  <input type="number" v-model.number="second"> =
  <input type="number" v-model="sum"> 
</form>

<script>
new Vue({
  el: '#myForm',
  data: {
    first: 0,
    second: 0
  },
  computed: {
    sum: function() {
      return this.first + this.second;
    }
  }
})
</script>

Одна из особенностей Vue - это вычисляемые свойства. Это виртуальные значения, которые распознают, когда обновляются их производные значения. В приведенном выше коде, как только любое из двух полей формы изменится, сумма будет обновлена. Это работает и вне полей формы. Мы могли бы найти сумму следующим образом:

The total is {{sum}}.

Работа с Ajax

В jQuery просто использовать Ajax. А что предлагает Vue, чтобы облегчить работу с Ajax?

Vue.js позволяет разработчикам самостоятельно решать, как обрабатывать HTTP-запросы. Первое, что нужно рассмотреть, это Axios - библиотеку, созданную на базе Promise. Вот простой пример (взят из README- файла этой библиотеки):

axios.get('/user?ID=12345')
  .then(function (response) {
    // успешная обработка
    console.log(response);
  })
  .catch(function (error) {
    // ошибка обработки
    console.log(error);
  })
  .then(function () {
    // всегда выполняется
  });

Разумеется, Axios поддерживает запросы POST и позволяет указывать заголовки.

Но я являюсь большим поклонником Fetch. Это не внешняя библиотека, а стандартный способ выполнения HTTP-запросов. Fetch хорошо поддерживается браузерами.

Fetch создан на основе Promise и имеет дружественный API:

fetch('http://example.com/movies.json')
  .then(function(response) {
    return response.json();
  })
  .then(function(myJson) {
    console.log(JSON.stringify(myJson));
  });

И Axios, и Fetch охватывают все типы HTTP-запросов.

Давайте проведем простое сравнение. Вот реализация на jQuery, в которой используется API Star Wars.

<h1>Star Wars Films</h1>
<ul id="films">
</ul>

<script>
$(document).ready(function() {
  $.get('https://swapi.com/api/films', function(res) {
    let list = '';
    res.results.forEach(function(r) {
      list += `<li>${r.title}</li>`;
    });
    $('#films').html(list);
  });
});
</script>

В приведенном выше примере я использую $.get, чтобы вызвать API и вернуть список фильмов. Затем я создаю список заголовков в виде элементов списка li с этими данными и вставляю их в ul.

Рассмотрим аналогичный пример, в котором используется Vue:

<div id="app">
  <h1>Star Wars Films</h1>
  <ul>
    <li v-for="film in films">{{film.title}}</li>
  </ul>  
</div>

<script>
const app = new Vue({
  el: '#app',
  data: {
    films: []
  }, 
  created() { 
    fetch('https://swapi.com/api/films')
    .then(res => res.json())
    .then(res => {
      this.films = res.results;  
    });
  }
})
</script>

Лучшая часть приведенного выше кода - использование шаблона v-for. Внедрение Vue делает процесс более естественным.

Полный пример

Допустим, клиент попросил создать современный интерфейс поиска с поддержкой Ajax для API товара. В реализованном решении должны присутствовать:

  • Фильтрация по названию и категории товара.
  • Валидация формы по поисковому запросу или категории.
  • Пока API запрашивается, пользователю должно отображаться сообщение, а кнопка отправки должна быть отключена.
  • После завершения поиска вывести сообщение о том, что товары не были найдены или список найденных.

Давайте начнем с jQuery-версии. Код HTML:

<form>
  <p>
    <label for="search">Search</label>
    <input type="search" id="search">
  </p>
  <p>
    <label for="category">Category</label>
    <select id="category">
      <option></option>
      <option>Food</option>
      <option>Games</option>
    </select>
  </p> 
  <button id="searchBtn">Search</button>
</form>

<div id="status"></div>
<div id="results"></div>

У нас форма с двумя фильтрами и двумя блоками div. Теперь код.

const productAPI = 'https://wt-c2bde7d7dfc8623f121b0eb5a7102930-0.sandbox.auth0-extend.com/productSearch';

$(document).ready(() => {
  let $search = $('#search');
  let $category = $('#category');
  let $searchBtn = $('#searchBtn');
  let $status = $('#status');
  let $results = $('#results');
  
  $searchBtn.on('click', e => {
    e.preventDefault();
    
    // Сначала очищаем предыдущие элементы
    $status.html('');
    $results.html('');

    // теперь выполняем валидацию формы
    let term = $search.val();
    let category = $category.val();
    if(term === '' && category === '') {
      $status.html('You must enter a term or select a category.');
      return false;
    }

    $searchBtn.attr('disabled','disabled');
    $status.html('Searching - please stand by...');
    
    $.post(productAPI, { name:term, category:category }, body => {
      $searchBtn.removeAttr('disabled');
      $status.html('');

      if(body.results.length === 0) {
        $results.html('<p>Sorry, no results!</p>');
        return;
      }
      
      let result = '<ul>';
      body.results.forEach(r => {
        result += `<li>${r.name}</li>`
      });
      result += '</ul>';
      $results.html(result);
    });
    
  });
});

Приведенный выше код начинается с создания набора переменных для полей формы, кнопок и элементов div.

Логика реализации находится в обработчике нажатия кнопки. Мы выполняем валидацию. Если все в порядке, отправляем POST-запрос к API. После получения ответа отображаем результаты или сообщение, что ничего не найдено.

Теперь рассмотрим Vue-версию. Начинаем с HTML:

<div id="app">
  <form>
    <p>
      <label for="search">Search</label>
      <input type="search" v-model="search">
    </p>
    <p>
      <label for="category">Category</label>
      <select v-model="category">
        <option></option>
        <option>Food</option>
        <option>Games</option>
      </select>
    </p>
    <button @click.prevent="searchProducts" :disabled="searchBtnDisabled">Search</button>
  </form>

    <div v-html="status"></div>
    <ul v-if="results">
      <li v-for="result in results">{{result.name}}</li>
    </ul>
</div>

В приведенном выше коде:

  • Макет обернут в div, который можно использовать, чтобы сообщить Vue, где работать.
  • Используется директива v-model в полях формы для облегчения работы с данными.
  • Используется @click.prevent для обработки основной операции поиска.
  • Используется :disabled для проверки т, привязана ли кнопка к значению в Vue-приложении.
  • Используется v-if для условного отображения списка результатов v-for и обработки итерации.

Теперь посмотрим на код приложения.

const productAPI = 'https://wt-c2bde7d7dfc8623f121b0eb5a7102930-0.sandbox.auth0-extend.com/productSearch';

const app = new Vue({
  el: '#app',
  data: {
    search: '',
    category: '',
    status: '',
    results: null,
    searchBtnDisabled: false
  },
  methods: {
    searchProducts:function() {
      this.results = null;
      this.status = '';
      
      if(this.search === '' && this.category === '') {
        this.status = 'You must enter a term or select a category.';
        return;
      }

      this.searchBtnDisabled = true;
      this.status = 'Searching - please stand by...';
      
      fetch(productAPI, {
        method: 'POST',
        headers: {
          'Content-Type':'application/json'
        },
        body: JSON.stringify({name:this.search,category:this.category})
      }).then(res => res.json())
      .then(res => {
        this.status = '';
        this.searchBtnDisabled = false;
        this.results = res.results;
        if(this.results.length === 0) this.status = '<p>Sorry, no results!</p>';
      });
      
    }
  }
});

Первый блок, который должен быть вызван - это набор data полей. Некоторые из них соответствуют полям формы, а другие - результатам, сообщениям о состоянии и т. п.

Метод searchProducts обрабатывает многое из того, что и  JQuery- версия. Но в нем намного кода, связанного с DOM.

JQuery умер! Да здравствует Vue!

Vue может стать следующим шагом для тех людей, которые привыкли работать с jQuery. Данный фреймворк позволят создавать сложные приложения, и включает в себя отличный инструмент командной строки. К тому же Vue может использоваться как замена jQuery при решении более простых задач.

Вадим Дворниковавтор-переводчик статьи «Making the Move from jQuery to Vue»