Coding With Fun
Home Docker Django Node.js Articles Python pip guide FAQ Policy

Vue.js 2.0 calculates properties


May 07, 2021 Vue.js 2.0


Table of contents


The calculated property

Binding expressions in a template is convenient, but they are actually for simple operations. P utting too much logic into a template can make the template too heavy and difficult to maintain. For example:

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

In this case, the template is no longer simple and clear. Y ou should confirm the message before implementing it in reverse. This problem gets worse when you display messages in reverse more than once.

This is why any complex logic that you should use to calculate properties.

Basic examples

<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: {
    // a computed getter
    reversedMessage: function () {
      // `this` points to the vm instance
      return this.message.split('').reverse().join('')
    }
  }
})

Results:

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

Here we declare a calculated property reversedMessage. The function we provide will be used as a getter for the property vm.reversedMessage.

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

You can open the browser's console and modify vm . The value of vm.reversedMessage always depends on the value of vm.message.

You can bind calculated properties in a template just like you would a normal property. V ue knows that vm.reversedMessage relies on vm.message, so when vm.message changes, bindings that depend on vm.reversedMessage are updated. And the best part is that we create this dependency in a declarate way: the getter of calculated properties is clean and side-effect-free, and therefore easy to test and understand.

Calculate cache vs Methods

You may have noticed that we can do the same by calling the method in the expression:

<p>Reversed message: "{{ reverseMessage() }}"</p>
// in component
methods: {
  reverseMessage: function () {
    return this.message.split('').reverse().join('')
  }
}

Without calculating the property, we can define the same function in the method to replace it. F or the end result, the two approaches are indeed the same. T he difference, however, is that the calculated property is based on its dependent cache. T he calculated property is re-valued only if its dependents change. This means that as long as the message does not change, multiple accesses to the reversedMessage calculation property immediately return the results of the previous calculation without having to execute the function again.

This also means that the following calculated properties will not be updated because Date.now() is not a responsive dependency:

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

In contrast, method calls always execute functions whenever they are re-rendered.

Why do we need a cache? S uppose we have an important calculation attribute A, which requires a huge array traversal and a lot of calculations. T hen we may have other computational properties that depend on A. I f there is no cache, we will inevitably execute A's getter multiple times! If you don't want to have a cache, use method instead.

Calculating properties vs Watched Property

Vue .js provides a method $watch to observe data movements on Vue instances. W hen some data needs to change based on other data, $watch attractive -- especially if you're from AngularJS. H owever, it is generally better to use calculated properties instead of a commanded $watch callbacks. Consider the following 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 command-based and repetitive. Compared to calculated properties:

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

That's better, isn't it?

Calculate setter

Calculating properties is only getter by default, but you can also provide a setter when you need it:

// ...
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 you run vm.fullName with 'John Doe', the setter is called, and vm.firstName and vm.lastName are updated accordingly.

Observe Watchers

Although calculating properties is more appropriate in most cases, sometimes a custom watcher is also required. T his is why Vue provides a more general way to respond to changes in data through the watch option. This is useful when you want to perform asynchronous or expensive operations when the data changes in response.

For example:

<div id="watch-example">
  <p>
    Ask a yes/no question:
    <input v-model="question">
  </p>
  <p>{{ answer }}</p>
</div>
<!-- Since there is already a rich ecosystem of ajax libraries    -->
<!-- and collections of general-purpose utility methods, Vue core -->
<!-- is able to remain small by not reinventing them. This also   -->
<!-- gives you the freedom to just use what you're familiar with. -->
<script src="https://unpkg.com/[email protected]/dist/axios.min.js" rel="external nofollow" ></script>
<script src="https://unpkg.com/[email protected]/lodash.min.js" rel="external nofollow" ></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) {
      this.answer = 'Waiting for you to stop typing...'
      this.getAnswer()
    }
  },
  methods: {
    // _.debounce 是一个通过 lodash 限制操作频率的函数。
    // 在这个例子中,我们希望限制访问yesno.wtf/api的频率
    // ajax请求直到用户输入完毕才会发出
    // 学习更多关于 _.debounce function (and its cousin
    // _.throttle), 参考: https://lodash.com/docs#debounce
    getAnswer: _.debounce(
      function () {
        var vm = this
        if (this.question.indexOf('?') === -1) {
          vm.answer = 'Questions usually contain a question mark. ;-)'
          return
        }
        vm.answer = 'Thinking...'
        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
          })
      },
      // 这是我们为用户停止输入等待的毫秒数
      500
    )
  }
})
</script>
Results:

Ask a yes/no question:

I cannot give you an answer until you ask a question!


In this example, using the watch option allows us to perform asynchronous operations (access to an API), limits how often we do so, and sets the intermediate state between us before we get the final result. This is not possible to calculate properties.

In addition to the watch option, you can use vm.$watch API commands.