Unetway

Вычисляемые свойства и слежение

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

<div id="example">
  {{ message.split('').reverse().join('') }}
</div>

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

<div id="example">
  <p>Изначальное сообщение: "{{ message }}"</p>
  <p>Сообщение задом наперёд: "{{ reversedMessage }}"</p>
</div>
var vm = new Vue({
  el: '#example',
  data: {
    message: 'Привет'
  },
  computed: {
    // геттер вычисляемого значения
    reversedMessage: function () {
      // `this` указывает на экземпляр vm
      return this.message.split('').reverse().join('')
    }
  }
})

В примере добавлено вычсляемое свойство reversedMessage. Функия будет спользоваться как геттер свойства vm.reversedMessage:

console.log(vm.reversedMessage) // => 'тевирП'
vm.message = 'Пока'
console.log(vm.reversedMessage) // => 'акоП'

Значение vm.reversedMessage всегда зависит от значения vm.message.

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

Кеширование вычисляемых свойств

Такой же результат можно достигнуть при помощи метода:

<p>Сообщение задом наперёд: "{{ reverseMessage() }}"</p>
// в компоненте
methods: {
  reverseMessage: function () {
    return this.message.split('').reverse().join('')
  }
}

Вместо вычисляемого свойства, можно указать ту же самую функцию в качестве метода. Оба подхода делают одно и то же. Но есть различие: вычисляемые свойства кешируются, основываясь на своих зависимостях. Вычисляемое свойство будет пересчитано только тогда, когда изменится одна из его зависимостей. Поэтому, пока message остаётся неизменным, обращение к reversedMessage будет возвращать единожды вычисленное значение, не запуская функцию вновь.
Следующее вычисляемое свойство не обновится, так как Date.now() не является реактивной зависимостью:

computed: {
  now: function () {
    return Date.now()
  }
}

Использование метода, напротив, запускает функцию всегда, при каждом обращении к нему. Использование метода, запускает функцию при каждом обращении.

Вычисляемые свойства и слежение

Vue предоставляет более общий способ наблюдения и реагирования на изменения данных в экземпляре: слежение за свойствами. 

<div id="demo">{{ fullName }}</div>
var vm = new Vue({
  el: '#demo',
  data: {
    firstName: 'Foo',
    lastName: 'Bar',
    fullName: 'Foo Bar'
  },
  watch: {
    firstName: function (val) {
      this.fullName = val + ' ' + this.lastName
    },
    lastName: function (val) {
      this.fullName = this.firstName + ' ' + val
    }
  }
})

Код выше — императивный и избыточный. Вычисляемое свойство сделает его проще:

var vm = new Vue({
  el: '#demo',
  data: {
    firstName: 'Foo',
    lastName: 'Bar'
  },
  computed: {
    fullName: function () {
      return this.firstName + ' ' + this.lastName
    }
  }
})

Сеттеры вычисляемых свойств

По умолчанию вычисляемые свойства работают только на чтение, но в случае необходимости вы можете также указать и сеттер:

// ...
computed: {
  fullName: {
    // геттер:
    get: function () {
      return this.firstName + ' ' + this.lastName
    },
    // сеттер:
    set: function (newValue) {
      var names = newValue.split(' ')
      this.firstName = names[0]
      this.lastName = names[names.length - 1]
    }
  }
}
// ...

Запись vm.fullName = 'Иван Иванов' вызовет сеттер, и vm.firstName и vm.lastName будут обновлены.

Методы-наблюдатели

Конечо, лучше использовать вычисляемые свойства, иногда пользовательские методы-наблюдатели необходимы. 
Vue предоставляет реагирование на изменения в данных через опцию watch. Она полезна для дорогих или асинхронных операций, выполняемых в ответ на изменение данных.

<div id="watch-example">
  <p>
    Задайте вопрос, на который можно ответить "да" или "нет":
    <input v-model="question">
  </p>
  <p>{{ answer }}</p>
</div>
<!-- Поскольку уже существует обширная экосистема ajax-библиотек  -->
<!-- и библиотек функций общего назначения, ядро Vue может        -->
<!-- оставаться маленьким и не изобретать их заново. Кроме того,  -->
<!-- это позволяет вам использовать только знакомые инструменты. -->
<script src="https://cdn.jsdelivr.net/npm/axios@0.12.0/dist/axios.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/lodash@4.13.1/lodash.min.js"></script>
<script>
var watchExampleVM = new Vue({
  el: '#watch-example',
  data: {
    question: '',
    answer: 'Пока вы не зададите вопрос, я не могу ответить!'
  },
  watch: {
    // эта функция запускается при любом изменении вопроса
    question: function (newQuestion) {
      this.answer = 'Ожидаю, когда вы закончите печатать...'
      this.getAnswer()
    }
  },
  methods: {
    // _.debounce — это функция из lodash, позволяющая ограничить
    // то, насколько часто может выполняться определённая операция.
    // В данном случае, мы хотим ограничить частоту обращений к yesno.wtf/api,
    // дожидаясь завершения печати вопроса перед тем как послать ajax-запрос.
    // Чтобы узнать больше о функции _.debounce (и её родственнице _.throttle),
    // см. документацию: https://lodash.com/docs#debounce
    getAnswer: _.debounce(
      function () {
        if (this.question.indexOf('?') === -1) {
          this.answer = 'Вопросы обычно заканчиваются вопросительным знаком. ;-)'
          return
        }
        this.answer = 'Думаю...'
        var vm = this
        axios.get('https://yesno.wtf/api')
          .then(function (response) {
            vm.answer = _.capitalize(response.data.answer)
          })
          .catch(function (error) {
            vm.answer = 'Ошибка! Не могу связаться с API. ' + error
          })
      },
      // Это число миллисекунд, которое мы ждём, после того как пользователь прекратил печатать.
      500
    )
  }
})
</script>

Применение опции watch позволяет выполнять асинхронную операцию, ограничивать частоту выполнения этой операции и устанавливать промежуточные состояния до получения окончательного ответа. Также можно использовать vm.$watch в императивном стиле.