Computed properties and listeners


Directory


Computed Property


Template The expressions within are very convenient, but they are designed for simple operations. Putting too much logic into a template can make it overweight and difficult to maintain. For example:

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

Here, the template is no longer simple declarative logic. You have to watch for a while to realize that here is a flipped string that wants to display the variable message. It becomes more difficult to deal with when you want to reference the flipped string here multiple times in the template.

So, for any complex logic, you should use Computed properties.


Basic example

<div id="example">
  <p>Original message: "{{ message }}"</p>
  <p>Computed reversed message: "{{ reversedMessage }}"</p>
</div>
var vm = new Vue({
  el: '#example',
  data: {
    message: 'Hello'
  },
  computed: {
    // 计算属性的 getter
    reversedMessage: function () {
      // `this` 指向 vm 实例
      return this.message.split('').reverse().join('')
    }
  }
})

Result:

Original message: "Hello"
Computed reversed message: "olleH"

Here we declare a Computed property reversedMessage. The function we provide will be used as the getter function of the property vm.reversedMessage:

console.log(vm.reversedMessage) // => 'olleH'
vm.message = 'Goodbye'
console.log(vm.reversedMessage) // => 'eybdooG'

You can open the browser console and modify the vm in the example yourself. The value of vm.reversedMessage always depends on the value of vm.message.

You can bind computed properties in templates just like normal properties. Vue knows that vm.reversedMessage depends on vm.message, so when vm.message changes, all dependencies on vm.reversedMessage Bindings are also updated. And the best part is that we've created this dependency declaratively: the computed property's getter function has no side effects, making it easier to test and understand.


Computed property cache vs method

You may have noticed that we can pass in the expression Calling a method to achieve the same effect:

<p>Reversed message: "{{ reversedMessage() }}"</p>
// 在组件中
methods: {
  reversedMessage: function () {
    return this.message.split('').reverse().join('')
  }
}

We can define the same function as a method instead of a computed property. The end result is indeed exactly the same both ways. However, the difference is that computed properties are cached based on their responsive dependencies. They are only re-evaluated when the associated reactive dependencies change. This means that as long as message has not changed, multiple accesses to the reversedMessage computed property will immediately return the previous calculation result without having to execute the function again.

This also means that the calculated property below will no longer be updated, because Date.now() is not a reactive dependency:

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

In contrast, whenever a re-redirect is triggered When rendering, the calling method will alwaysexecute the function again.

Why do we need caching? Suppose we have a computational property A that has a high performance overhead. It requires traversing a huge array and doing a lot of calculations. Then we might have other computed properties that depend on A. Without caching, we will inevitably execute the getter of A multiple times! If you don't want caching, use methods instead.


Computed properties vs listening properties

Vue provides a more general way to observe And respond to data changes on the Vue instance: Listening properties. It's easy to abuse watch when you have some data that needs to change as other data changes - especially if you've used AngularJS before. However, it is often better to use computed properties instead of imperative watch callbacks. Consider this example:

<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
    }
  }
})

The above code is imperative and repetitive. Compare it to the computed property version:

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

Much better, isn't it?


Setters for computed properties

Computed properties only have getters by default, but you can also use them when needed Provide a setter:

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

Now when running vm.fullName = 'John Doe', the setters will be called, vm.firstName and vm. lastName will also be updated accordingly.


Listener


While computed properties are more appropriate in most cases, But sometimes a custom listener is needed. This is why Vue provides a more general way to respond to data changes through the watch option. This approach is most useful when you need to perform asynchronous or expensive operations when data changes.

For example:

<div id="watch-example">
  <p>
    Ask a yes/no question:
    <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: 'I cannot give you an answer until you ask a question!'
  },
  watch: {
    // 如果 `question` 发生改变,这个函数就会运行
    question: function (newQuestion, oldQuestion) {
      this.answer = 'Waiting for you to stop typing...'
      this.debouncedGetAnswer()
    }
  },
  created: function () {
    // `_.debounce` 是一个通过 Lodash 限制操作频率的函数。
    // 在这个例子中,我们希望限制访问 yesno.wtf/api 的频率
    // AJAX 请求直到用户输入完毕才会发出。想要了解更多关于
    // `_.debounce` 函数 (及其近亲 `_.throttle`) 的知识,
    // 请参考:https://lodash.com/docs#debounce
    this.debouncedGetAnswer = _.debounce(this.getAnswer, 500)
  },
  methods: {
    getAnswer: function () {
      if (this.question.indexOf('?') === -1) {
        this.answer = 'Questions usually contain a question mark. ;-)'
        return
      }
      this.answer = 'Thinking...'
      var vm = this
      axios.get('https://yesno.wtf/api')
        .then(function (response) {
          vm.answer = _.capitalize(response.data.answer)
        })
        .catch(function (error) {
          vm.answer = 'Error! Could not reach the API. ' + error
        })
    }
  }
})
</script>

Result:

1.gif

##In this example, using the

watch option allows us to perform async Operation (accessing an API), limiting how often we perform the operation, and setting intermediate states before we get the final result. These are things that computed properties cannot do.

In addition to the

watch option, you can also use the imperative vm.$watch API.