Vue.js


Я не понимаю Angular. Мне очень нравится React, но я все еще изучаю его основы. Давайте попробуем Vue. Я расскажу, как я сделал микро-викторину.


Инициализация


HTML


Сначала создайте небольшой HTML-скелет и подключите Vue.


<html>
<body>
  <!-- Vue работает с этим div-ом -->
  <div id="app">
    <!-- Значение data.title из Vue отобразится здесь -->
    <h1>{{ title }}</h1>
  </div>

  <!-- Загружаем скрипт Vue -->
  <script src="https://vuejs.org/js/vue.js"></script>
  <!-- После загрузки страницы инициализируем Vue -->
  <script>
    window.onload=function(){
      new Vue({
        el: '#app', // Vue работает с этим div-ом (Смотри строку 4)
        data: { title: 'Hello.' }, // title установлен в значение 'Hello.'
      });
    }
  </script>
</body>

Сохраните его как foo.html и откройте в браузере. Вы должны увидить Hello., — это значит, что все работает. Мы только что научились отображать данные с помощью синтаксиса Mustache: {{ title }}


Данные


Затем создайте простой объект викторины и добавьте его к данным экземпляра Vue.


<script>
    window.onload=function(){
    // Создайте викторину с заголовком и двумя вопросами.
    // Вопрос имеет один или несколько ответов и один или несколько ответов могут быть верными.
      var quiz = {
        title: 'Моя викторина',
        questions: [
          {
            text: "Вопрос 1",
            responses: [
              {text: 'Неправильно, очень плохо.'}, 
              {text: 'Правильно!', correct: true}, 
            ]
          }, {
            text: "Вопрос 2",
            responses: [
              {text: 'Правильный ответ', correct: true}, 
              {text: 'Неправильный ответ'}, 
            ]
          } 
        ]
      };

      new Vue({
        el: '#app',
        data: { quiz: quiz }, // Добавьте объект викторины к данным Vue
      });
    }
</script>

Это наша викторина, следующий шаг — отобразить ее с помощью циклов Vue.


Отображение данных в HTML


Vue предоставляет декларативную систему циклов добавлением в элемент HTML аттрибута v-for="item on items".


<div id="app">
  <!-- Заголовок викторины -->
  <h1>{{ quiz.title }}</h1>
  <!-- Вопросы: показывать div для каждого вопроса -->
  <div v-for="question in quiz.questions">
    <h2>{{ question.text }}</h2>
    <!-- Ответы: Показывать li с радио-кнопкой для каждого возможного ответа -->
    <ol>
      <li v-for="response in question.responses">
        <label>
          <input type="radio"> {{response.text}}
        </label>
      </li>
    </ol>
  </div>
</div>

Vue имеет ненавязчивую реактивную систему. Если объект данных изменяется, HTML-отображение обновляется. Теперь викторина полностью готова, она живая и реактивная.



До сих пор все вопросы отображались на одной странице. Давайте изменим викторину так, чтобы отображать только один вопрос на странице. Чтобы сделать это, мы должны добавить кнопки «Следующий» и «Предыдущий» для каждого вопроса и скрыть все вопросы, которые не являются текущим.


Экземпляр Vue: методы навигации


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


new Vue({
  el: '#app',
  data: {
    quiz: quiz,
    // Хранит индекс текущего вопроса
    questionIndex: 0,
  },
  // Представление вызовет эти методы при клике
  methods: {
    // Перейти к следующему вопросу
    next: function() {
      this.questionIndex++;
    },
    // Вернуться к предыдущему вопросу
    prev: function() {
      this.questionIndex--;
    }
  }
});

Поле questionIndex увеличивается с помощью next() и уменьшается с помощью prev()


HTML: кнопки навигации


Создайте кнопку с директивой v-on:click="next", чтобы вызвать метод next(). Добавьте v-show="index === questionIndex" в контейнер вопросов, чтобы отображать только текущий вопрос (не забудьте добавить параметр index в директиву v-for)


<div id="app">
  <h1>{{ quiz.title }}</h1>
  <!-- Индекс используется для проверки текущего индекса вопроса -->
  <div v-for="(question, index) in quiz.questions">
    <!-- Скрыть все вопросы, показывать только один вопрос с текущим индексом -->
    <div v-show="index === questionIndex">
      <h2>{{ question.text }}</h2>
      <ol>
        <li v-for="response in question.responses">
          <label>
            <input type="radio"> {{response.text}}
          </label>
        </li>
      </ol>
      <!-- Кнопки навигации -->
      <!-- Заметка: Кнопка "Предыдущий" должна быть скрыта для первого вопроса -->
      <button v-if="questionIndex > 0" v-on:click="prev">
        Предыдущий
      </button>
      <button v-on:click="next">
        Следующий
      </button>
    </div>
  </div>
</div>

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


Результат викторины


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


Обработка ответов пользователей


Прямо сейчас, радио-кнопки викторины не имеют привязки к Vue: клик по радио-кнопке не имеет эффекта. Директива v-model создает двустороннюю связь между вводом формы и состоянием приложения. Замените ранее созданную радио-кнопку.


<!-- Для каждого ответа текущего вопроса -->
<li v-for="response in question.responses">
  <label>
    <!-- Радио-кнопка имеет 3 новые директивы -->
    <!-- v-bind:value устанавливает "value" в значение "true" если ответ правильный -->
    <!-- v-bind:name устанавливает "name" в значение индекса вопроса для группировки ответов по вопросу -->
    <!-- v-model создает связь с userResponses -->
    <input type="radio" 
           v-bind:value="response.correct" 
           v-bind:name="index" 
           v-model="userResponses[index]"> {{response.text}}
  </label>
</li>

Атрибуты name и value привязаны к данным Vue. Затем инициализируйте userResponses в экземпляре Vue.


new Vue({
  el: '#app',
  data: {
    quiz: quiz,
    questionIndex: 0,
    // Массив инициализируется со значением "false" для всех вопросов
    // Это означает: "Пользователь верно ответил на вопрос n?" "Нет".
    userResponses: Array(quiz.questions.length).fill(false)
  },
  methods: {
    // ...
  }
});

Это был последний шаг для обработки пользовательских ответов: userResponses содержит живой массив «true, если ответ правильный». Вы можете перемещаться вперед и назад, выбирать и изменять ответы и видеть обновления userResponses.


Показ счета


Конечный счет — сумма «true» значений в userResponses. Создайте метод в приложении Vue, чтобы вычислять этот счет.


new Vue({
  // ...
  methods: {
    // ...
    // Возвращает количесво "true" в userResponses
    score: function() {
      return this.userResponses.filter(function(val) { return val }).length;
    }
  }
});

Затем добавьте итоговую страницу викторины с общим счетом.


<div id="app">
  <h1>{{ quiz.title }}</h1>
  <div v-for="(question, index) in quiz.questions">
    <!-- //... -->
  </div>
  <!-- Последняя страница, викторина завершена, показываем счет -->
  <div v-show="questionIndex === quiz.questions.length">
    <h2>
    Викторина завершена
  </h2>
    <p>
      Счет: {{ score() }} / {{ quiz.questions.length }}
    </p>
  </div>
</div>

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


Послесловие


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

Комментарии (12)


  1. TimTowdy
    25.08.2017 00:56
    +1

    Ответ на question — это все-таки answer, а не response.


    1. Corpsee Автор
      25.08.2017 11:31
      +1

      Ну это авторские названия, не стал их менять в переводе.


  1. JSmitty
    25.08.2017 08:57
    +3

    Vue имеет ненавязчивую реактивную систему.

    Простите, что? Ненавязчивая — это когда есть ещё какие-то способы, кроме стандартного. Вся реактивность Vue — именно что навязчивая (безотносительно других фреймворков).

    У новичков в частности бывает такое — какие-то предварительно вычисляемые данные, инстансы библиотек (или ссылки на DOM) укладывают в data — ну, Vue.js послушно обвешивает всё своей реактивностью, хотя это просто никогда не нужно будет. В код-ревью с завидной регулярностью натыкаюсь на такое у разработчиков, которые с фреймворком уже год-полтора.

    Поэтому, когда вы изучите основы и поработаете с ним годик в production — то обязательно поймете, что реактивность Vue.js — именно навязчивая. И, если вы об этом не помните — обязательно словите вагон проблем.


    1. maksbotan
      25.08.2017 09:23
      +1

      А куда надо запихивать такие данные, чтобы они были доступны в шаблоне?


      (я новичок)


      1. Fractalzombie
        25.08.2017 11:04
        +1

        В data или vuex. Зависит от того, что нужно.


        1. acupofspirt
          26.08.2017 11:02
          +1

          Так ведь стейт vuex тоже реактивен?


      1. bingo347
        25.08.2017 11:29
        +1

        методы не реактивны, их возвращаемое значение можно вставить в шаблон


      1. TimTowdy
        25.08.2017 12:03
        +2

        Константы можно задать в хуке created():

        created(){
            this.PI = 3.14
        }

        PI будет доступна в шаблоне.


    1. Corpsee Автор
      25.08.2017 11:13

      Ценный комментарий, спасибо! Буду это учитывать.


    1. TimTowdy
      25.08.2017 11:57
      +1

      Я думаю под ненавязчивостью подразумевается то, что для изменения состояния не требуется каша из абсолютно неестественных this.setState() / Object.assign() и философствование об иммутабельности. Нужно изменить состояние — берите и меняйте.
      У Vue реактивность не торчит из всех дыр, в отличие от другого известного фреймворка-библиотеки.

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


  1. Darkside73
    25.08.2017 11:25
    +2

    score можно в computed перенести. И пора уже на ES6 переходить)


  1. Poccomaxa_zt
    25.08.2017 14:35
    -1

    Мои навыки в javascript немного хромают и я только начал заниматься Vue.

    При всём моём уважении, не подумайте, что хотел обидеть, но — так может стоит не с Vue начинать, а закрепить знания самого языка? Я не хочу писать много слов, мне кажется даже статья когда-то проскакивала, что сейчас люди учат фреймворки, а не пытаются понимать их…
    Ведь если Вы будете понимать принципы «ссылочных» объектов, замыканий и жизненного цикла события в доме — всё будет куда проще… Там и поймёте почему стали использовать redux или упомянутый в комментариях vuex…