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




> Vue.js для начинающих, урок 1: экземпляр Vue
> Vue.js для начинающих, урок 2: привязка атрибутов
> Vue.js для начинающих, урок 3: условный рендеринг
> Vue.js для начинающих, урок 4: рендеринг списков
> Vue.js для начинающих, урок 5: обработка событий
> Vue.js для начинающих, урок 6: привязка классов и стилей
> Vue.js для начинающих, урок 7: вычисляемые свойства
> Vue.js для начинающих, урок 8: компоненты

Цель урока


Нашей основной целью является вывод данных, описываемых свойствами объекта с данными brand и product, в виде единой строки.

Начальный вариант кода


Вот код, находящийся в index.html, в теге <body>, с которого мы начнём работу:

<div id="app">
  <div class="product">
    <div class="product-image">
      <img :src="image" />
    </div>

    <div class="product-info">
      <h1>{{ product }}</h1>
      <p v-if="inStock">In stock</p>
      <p v-else :class="{ outOfStock: !inStock }">Out of Stock</p>

      <ul>
        <li v-for="detail in details">{{ detail }}</li>
      </ul>
      <div
        class="color-box"
        v-for="variant in variants"
        :key="variant.variantId"
        :style="{ backgroundColor:variant.variantColor }"
        @mouseover="updateProduct(variant.variantImage)"
      ></div>

      <button
        v-on:click="addToCart"
        :disabled="!inStock"
        :class="{ disabledButton: !inStock }"
      >
        Add to cart
      </button>

      <div class="cart">
        <p>Cart({{ cart }})</p>
      </div>
    </div>
  </div>
</div>

Вот код main.js:

var app = new Vue({
    el: '#app',
    data: {
        product: 'Socks',
        brand: 'Vue Mastery',
        image: './assets/vmSocks-green.jpg',
        inStock: true,
        details: ['80% cotton', '20% polyester', 'Gender-neutral'],
        variants: [
          {
            variantId: 2234,
            variantColor: 'green',
            variantImage: './assets/vmSocks-green.jpg'    
          },
          {
            variantId: 2235,
            variantColor: 'blue',
            variantImage: './assets/vmSocks-blue.jpg' 
          }
        ],
        cart: 0
    },
    methods: {
      addToCart() {
        this.cart += 1
      },
      updateProduct(variantImage) {
        this.image = variantImage
      }
    }
})

Обратите внимание на то, что в объект с данными добавлено новое свойство с именем brand.

Задача


Нам надо, чтобы то, что хранится в brand и в product, было бы скомбинировано в одну строку. Другими словами, нам нужно вывести в теге <h1> текст Vue Mastery Socks, а не просто Socks. Для решения этой задачи нужно задаться вопросом о том, как можно конкатенировать два строковых значения, хранящихся в экземпляре Vue.

Решение задачи


Мы для решения этой задачи воспользуемся вычисляемыми свойствами. Так как эти свойства не хранят значения, а вычисляют их, давайте добавим в объект с опциями, используемый при создании экземпляра Vue, свойство computed и создадим вычисляемое свойство с именем title:

computed: {
  title() {
    return this.brand + ' ' + this.product;
  }
}

Полагаем, тут всё устроено очень просто и понятно. Когда вызывается метод title(), он выполняет конкатенацию строк brand и product, после чего возвращает полученную в результате новую строку.

Теперь нам осталось лишь вывести title в теге <h1> нашей страницы.

Сейчас этот тег выглядит так:

<h1>{{ product }}</h1>

А теперь мы сделаем его таким:

<h1>{{ title }}</h1>

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


Заголовок страницы изменился

Как видно, в заголовке выводится Vue Mastery Socks, а это значит, что мы всё сделали правильно.

Мы взяли два значения из данных экземпляра Vue и создали на их основе новое значение. Если значение brand когда-нибудь будет обновлено, например — в это свойство окажется записанной строка Vue Craftery, то вносить какие-то изменения в код вычисляемого свойства не потребуется. Это свойство будет продолжать возвращать корректную строку, которая теперь будет выглядеть как Vue Craftery Socks. В вычисляемом свойстве title всё ещё будет использоваться свойство brand, так же, как и раньше, но теперь в brand будет записано новое значение.

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

Более сложный пример


Сейчас мы обновляем изображение, выводимое на странице, используя метод updateProduct. Мы передаём ему variantImage, а затем записываем в свойство image то, что попало в метод после наведения мыши на соответствующий цветной квадрат. Соответствующий код выглядит так:

updateProduct(variantImage) {
  this.image = variantImage;
}

Этот механизм работает нормально, но если нам понадобится, основываясь на том, на какой именно цветной квадрат наведена мышь, менять не только изображение, но и что-то ещё, это будет означать необходимость рефакторинга данного кода. Давайте этим и займёмся.

А именно, вместо того, чтобы хранить в данных свойство image, заменим его на свойство selectedVariant. Инициализируем его значением 0.

selectedVariant: 0,

Почему 0? Дело в том, что мы планируем устанавливать это свойство на основе индекса (index) элемента, над которым находится указатель мыши. Мы можем добавить индекс в конструкцию v-for:

<div
  class="color-box"
  v-for="(variant, index) in variants"
  :key="variant.variantId"
  :style="{ backgroundColor:variant.variantColor }"
  @mouseover="updateProduct(variant.variantImage)"
></div>

Обратите внимание на то, что там, где раньше была конструкция v-for=«variant in variants», теперь находится код v-for=»(variant, index) in variants».

Теперь, вместо того, чтобы передавать variant.variantImage в updateProduct, передадим в этот метод index:

@mouseover="updateProduct(index)"

Теперь займёмся кодом метода updateProduct. Здесь мы получаем индекс. И, вместо записи нового значения в this.image, запишем index в this.selectedVariant. То есть, в selectedVariant попадёт значение index, соответствующее тому квадрату, на который был наведён указатель мыши. Ещё мы, в целях отладки, поместим в этот метод команду для логирования значения index.

updateProduct(index) {
  this.selectedVariant = index;
  console.log(index);
}

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


Проверка работоспособности созданного нами механизма

Однако изображение теперь на странице не выводится. В консоли появляется предупреждение.


Предупреждение, выводимое в консоль

Дело тут в том, что мы удалили свойство image, заменив его свойством selectedValue, но это свойство в нашем приложении всё ещё используется. Давайте исправим проблему, вернув image в экземпляр Vue, но на этот раз — в виде вычисляемого свойства. Соответствующий код будет выглядеть так:

image() {
  return this.variants[this.selectedVariant].variantImage;
}

Здесь мы возвращаем свойство variantImage элемента массива this.variants[this.selectedVariant]. В качестве индекса, по которому осуществляется доступ к элементу массива, используется свойство this.selectedVariant, которое равняется 0 или 1. Это, соответственно, даёт нам доступ к первому или ко второму элементу массива.

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

Сейчас, когда мы подвергли рефакторингу код метода updateProduct, который теперь обновляет состояние свойства selectedVariant, мы можем поработать и с другими данными, хранящимися в объектах из массива variants, с такими, как поле variantQuantity, которое мы сейчас добавим в объекты:

variants: [
  {
    variantId: 2234,
    variantColor: 'green',
    variantImage: './assets/vmSocks-green.jpg',
    variantQuantity: 10
  },
  {
    variantId: 2235,
    variantColor: 'blue',
    variantImage: './assets/vmSocks-blue.jpg',
    variantQuantity: 0
  }
],

Давайте избавимся от обычного свойства inStock и, как и при работе со свойством image, создадим новое вычисляемое свойство с тем же именем, значение, возвращаемое которым, будет основываться на selectedVariant и variantQuantity:

inStock(){
  return this.variants[this.selectedVariant].variantQuantity
}

Это свойство очень похоже на вычисляемое свойство image. Но теперь мы берём из соответствующего объекта не свойство variantImage, а свойство variantQuantity.

Если теперь навести указатель мыши на квадрат, количество товара, соответствующее которому, равняется нулю, в inStock попадёт 0, а 0 является в JavaScript значением, приводимым к логическому значению false. Из-за этого на странице будет выведено сообщение Out of Stock.

Обратите внимание на то, что кнопка тоже, как и ранее, правильно реагирует на установку inStock в 0.


Кнопка и надпись зависят от количества товара каждого вида

Почему всё продолжает правильно работать? Дело в том, что inStock всё ещё используется для привязки класса disableButton к нашей кнопке. Единственное различие нового варианта приложения и его предыдущего варианта заключается в том, что теперь inStock — это вычисляемое, а не обычное свойство.

Дополнительные сведения о вычисляемых свойствах


Вычисляемые свойства кешируются. То есть — результаты вычисления этих свойств сохраняются в системе до тех пор, пока не изменятся данные, от которых зависят эти результаты. В результате, когда изменится variantQuantity, кеш будет очищен. А когда к inStock обратятся в следующий раз, свойство вернёт новый результат, который и будет помещён в кеш.

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

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

Практикум


Добавьте в объект с данными, используемый при создании экземпляра Vue, новое логическое свойства onSale. Оно будет указывать на то, проводится ли распродажа. Создайте вычисляемое свойство sale, которое, на основе brand, product и onSale формирует строку, сообщающую о том, проводится ли сейчас распродажа или нет. Выведите эту строку в карточке товара.

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

> Вот решение задачи

Итоги


На этом занятии мы познакомились с вычисляемыми свойствами. Вот самое важное из того, что мы о них узнали:

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


Если вы выполняете домашние задания к этому курсу, расскажите, делаете ли вы исключительно то, что вам предлагается, или идёте дальше?

> Vue.js для начинающих, урок 1: экземпляр Vue
> Vue.js для начинающих, урок 2: привязка атрибутов
> Vue.js для начинающих, урок 3: условный рендеринг
> Vue.js для начинающих, урок 4: рендеринг списков
> Vue.js для начинающих, урок 5: обработка событий
> Vue.js для начинающих, урок 6: привязка классов и стилей
> Vue.js для начинающих, урок 7: вычисляемые свойства
> Vue.js для начинающих, урок 8: компоненты