Статьи

Реакционная способность в Vue.js и его ловушках

В Vue мы любим одну вещь — реактивность системы. Если мы изменим значение данных, это вызовет обновление страницы, чтобы отразить это изменение.

Например:

varapp = newVue({
  el: '#app',
  data: {
    message: "Hello World"
  }
});

setTimeout(function() {
  app.message = "Goodbye World";
}, 2000)

<divid="app">
  <!--Renders as "Hello World"...-->
  <!--Then after 2 seconds re-renders as "Goodbye World"-->
  {{ message }}
</div>

Свойства данных, как messageв этом примере, являются реактивными , что означает, что они будут вызывать повторную визуализацию при изменении.

Подводные камни автоматической конфигурации реактивности

Vue автоматически настраивает реактивность всякий раз, когда вы создаете свойство данных, вычисляемое свойство, привязываете подпорку и т. Д. Эта автоматическая настройка удобна при кодировании приложения, поскольку она может:

  • Сэкономьте нам время.
  • Сделайте наш код лаконичным.
  • Помогите минимизировать нашу познавательную нагрузку.

Это просто делает вещи проще. Но эта простота может вернуться, чтобы укусить нас! Подводный камень в том, что, как и в случае с автоматической машиной, автоматическая реактивность делает нас ленивыми, и когда она не работает, мы понятия не имеем, почему!

Когда хорошая реактивность становится плохой

Студент курса Ultimate Vue.js для разработчиков, которого я преподаю, на днях принес мне интересную проблему. Он работал над Vue.js Poster Shop , первым проектом курса, который требует от вас создания корзины покупок с помощью Vue.

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

varmyProduct = {
  id: 1,
  name: 'My Product',
  price: 9.99
};

Но когда вы добавляете товар в корзину, вам также нужно количество, с которым он справился таким способом:

functionaddToCart(id) {
  varitem = this.cart.findById(id);
  if (item) {
    item.qty++;
  } else {
    item.qty = 1;
    this.cart.push(item);
  }
}

addToCart(myProduct.id);

Логика метода заключается в следующем:

  • Найдите предмет в корзине.
  • Если это там, увеличьте количество.
  • Если его там нет, дайте ему 1 и добавьте в корзину.

Появляется проблема

Шаблон корзины покупок просто отображает список элементов корзины:

<ul class="shopping-cart">
  <li v-for="item in cart">{{ item.name }} x {{ item.qty }}</li>
  <!-- Renders: myProduct x 1 -->
</ul>

Проблема заключалась в том, что, независимо от значения qty, шаблон всегда отображал значение «1».

Первоначально я думал, что логика addToCartфункции должна быть неправильной. Но после некоторой отладки я обнаружил, что qtyсвойство действительно увеличивается при каждом вызове метода, так что это не проблема.

Как реактивность работает под капотом

Хотя в большинстве случаев система реактивности Vue вызывает легкую форму радости, она может вызвать замешательство и разочарование, если она не работает так, как вы ожидаете.

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

Добытчики и сеттеры

Поведение по умолчанию для объекта JavaScript при обращении к нему заключается в получении или изменении соответствующего свойства напрямую. Например:

varmyObj={
  a:"Hello World"
};

console.log(myObj.a)// "Hello World"

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

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

reactiveSetter ()

Итак, возвращаясь к нашему объекту продукта, который выглядел следующим образом, когда мы его определили:

{
  id:1,
  name:'My Item',
  price:9.99
}

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

Эти функции получения и установки имеют несколько заданий (проверьте исходный код ), но одно из заданий rectiveSetterсостоит в том, чтобы вызвать уведомление об изменении, которое приводит к повторному отображению страницы!

Предостережения

Это блестящая система, хотя и ошибочная. Если вы добавляете (или удаляете) свойство после создания экземпляра Vue (например, в методе или хуке жизненного цикла), Vue не знает об этом .

// In the addToCart method, called after instantiation
myProduct.qty=1;

Посмотрите и увидите, что хотя qtyобъект определен, для него нет методов получения / установки:

Обновление реактивных объектов

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

functionaddToCart(id) {
  varitem = this.cart.findById(id);
  if (item) {
    item.qty++;
  } else {
    // Don't add a property e.g. item.qty = 1;
    // Instead create a fresh object
    varnewItem = {
      id: item.id,
      name: item.name,
      price: item.price,
      qty: 1
    };
    this.cart.push(item);
  }
}

addToCart(myProduct.id);

Vue.set

Но если вы не хотите создавать новый объект, вы можете использовать Vue.set, чтобы установить новое свойство объекта. Этот метод гарантирует, что свойство создано как реактивное свойство и запускает обновления просмотра:

functionaddToCart(id) {
  varitem = this.cart.findById(id);
  if (item) {
    item.qty++;
  } else {
    // Don't add a property directly e.g. item.qty = 1;
    // Use Vue.set to ensure the property is reactive
    Vue.set(item, 'qty', 1);
    this.cart.push(item);
  }
}

addToCart(myProduct.id);

Массивы

Как и объекты, массивы являются реактивными и наблюдаются на предмет изменений. Также как объекты, у массивов есть предостережения для манипуляции. Vue переносит методы массива, такие как push, spliceи т. Д., Поэтому они также будут запускать обновления представления

Это невозможно при прямой установке элемента с индексом, например: 

// This array change will not be detected:
app.myArray[index]=newVal;

Опять Vue.setприходит на помощь

Vue.set(app.myArray,index,newVal);