VueJs, difference between computed property and wa

2019-06-14 21:02发布

On Vue.js documentation there is an example like below:

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 with a computed property version:

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

What are the situations when watchers more suitable than computed properties? How should i decide which to choose? Documentation keeps saying it is more "generic" but does not really put its purpose.

标签: vue.js vuejs2
6条回答
老娘就宠你
2楼-- · 2019-06-14 21:26

Vue.js is reactive

That means it is able to react to things like user input and data changes. I recommend reading up on the reactivity system to have a better understanding of the mechanics Vue is using under the hood when a data change is observed. There are three main ways to have your components make use of Vue’s reactive nature. These are Methods, Computed Properties and Watchers. Without some scrutiny, these options may seem interchangeable (and in some ways they are) but each of them has their best use case scenario. To help illustrate the examples, I will make a small grading app that allows a teacher to input test scores for students in their class, view the average grade and set up the scaffolding for an auto-save feature.

METHODS

TL;DR – Use methods when you want to alter a component’s state or when an event has occurred that isn’t necessarily related to the instance data being mutated. Methods can take arguments but do not keep track of any dependencies. When you use a method, it usually creates some side effect within the component and methods are run each time the component is reloaded. This means if the UI is updated very frequently, this method (and any other methods on the component) will run as well. This could cause performance issues or lag in the UI.

Below is the beginning of our grading app. There is no validation or anything and its not pretty, I know. We have a small set of tests in our data object (student name and score). And a method we can use to add another test object to our data property ‘tests’.

new Vue({
  el: "#app",
  data: {
    newTest: {
      studentName: '',
      score: 0
    },
    tests: [{
      studentName: "Billy",
      score: 76
    }, {
      studentName: "Suzy",
      score: 85
    }, {
      studentName: "Johnny",
      score: 89
    }, {
      studentName: "Emma",
      score: 93
    }]
  },
  methods: {
    addTestScore: function() {
      this.tests.push({
        studentName: this.newTest.studentName,
        score: this.newTest.score
      });
      this.newTest.studentName = '';
      this.newTest.score = 0;
    }
  }
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.js"></script>

<body>
  <div id="app">
    <ul>
      <li v-for="test in tests">
        {{test.studentName}} - {{test.score}}
      </li>
    </ul>
    <span>Student</span>
    <input v-model="newTest.studentName">
    <span>Score</span>
    <input v-model="newTest.score">
    <button @click="addTestScore">Add </button>
  </div>
</body>

COMPUTED PROPERTIES

TL;DR – Use a computed property when you want to mutate a property that is dependent upon another property being changed. Computed properties are typically dependent on other data properties. Any change to the dependent properties will trigger the logic for the computed property. Computed properties are cached based on their dependencies so they will only rerun if a dependency changes. (For example, a computed property that returns a new Date() will never rerun because the logic will never run more than 1 time) Computed properties are getters by default but a setter function can be set if needed to achieve similar functionality.

In our grading app, we want to keep track of the average test score as we enter more data. Let's add a computed property called ‘average’ that will return the average score of the tests in our dataset. The ‘average’ computed property will be updated any time we add another test score.

new Vue({
  el: "#app",
  data: {
    newTest: {
      studentName: '',
      score: 0
    },
    tests: [{
      studentName: "Billy",
      score: 76
    }, {
      studentName: "Suzy",
      score: 85
    }, {
      studentName: "Johnny",
      score: 89
    }, {
      studentName: "Emma",
      score: 93
    }]
  },
  computed: {
    average: function() {
      var sum = this.tests.reduce(function(acc, test) {
        return acc + Number(test.score);
      }, 0);
      return (sum / this.tests.length).toFixed(2);
    }
  },
  methods: {
    addTestScore: function() {
      this.tests.push({
        studentName: this.newTest.studentName,
        score: this.newTest.score
      });
      this.newTest.studentName = '';
      this.newTest.score = 0;
    }
  }
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.min.js"></script>

<body>
  <div id="app">
    <span>Average Score: {{average}}</span>
    <ul>
      <li v-for="test in tests">
        {{test.studentName}} - {{test.score}}
      </li>
    </ul>
    <span>Student</span>
    <input v-model="newTest.studentName">
    <span>Score</span>
    <input v-model="newTest.score">
    <button @click="addTestScore">Add</button>
  </div>
</body>

WATCHERS

TL;DR – Use watchers when you need to perform some logic as a result of a change that took place on a specific data property. Watched properties only act on one property. This is most useful when you want to perform asynchronous or expensive operations in response to changing data. Keep in mind watchers only change when that specific data property changes.

Let us pretend the end user of our little grading app is a professor with 300 tests to grade. That can take a long time. An auto-save feature would be nice to have in case our end user gets to the end of the pile of tests and forgets to manually hit save. In our code lets add a watcher to our previously created computed property ‘average’. Whenever it is changed (as a result of a new test score being added and the average being updated) let's call a new ‘autosave’ method that could be used to call an API and save our test scores.

new Vue({
  el: "#app",
  data: {
    newTest: {
      studentName: '',
      score: 0
    },
    tests: [{
      studentName: "Billy",
      score: 76
    }, {
      studentName: "Suzy",
      score: 85
    }, {
      studentName: "Johnny",
      score: 89
    }, {
      studentName: "Emma",
      score: 93
    }]
  },
  watch: {
    average: function() {
      this.autosave();
    }
  },
  computed: {
    average: function() {
      var sum = this.tests.reduce(function(acc, test) {
        return acc + Number(test.score);
      }, 0);
      return (sum / this.tests.length).toFixed(2);
    }
  },
  methods: {
    addTestScore: function() {
      this.tests.push({
        studentName: this.newTest.studentName,
        score: this.newTest.score
      });
      this.newTest.studentName = '';
      this.newTest.score = 0;
    },
    autosave: function() {
    //pretend we are calling our backend to save the data
      console.log('calling api, saving data');
    }
  }
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.js"></script>

<body>
  <div id="app">
    <span>Average Score: {{average}}</span>
    <ul>
      <li v-for="test in tests">
        {{test.studentName}} - {{test.score}}
      </li>
    </ul>
    <span>Student</span>
    <input v-model="newTest.studentName">
    <span>Score</span>
    <input v-model="newTest.score">
    <button @click="addTestScore">Add</button>
  </div>
</body>

查看更多
Root(大扎)
3楼-- · 2019-06-14 21:28

Computed Properties

A computed property sample:

computed: {
   val () {
     return this.someDataProperty * someOtherVariable
   }
}

what does this particular piece of code do?

  1. It creates a property named val for the component (on the prototype so <vueInstanece>.hasOwnProperty('val') would show false).

  2. It has a dependency tree which consists of reactive properties (data properties, other computed properties) in this case : this.someDataProperty, which means the moment the dependencies change, the computed property will be recalculated.

  3. Although debated, can't have arguments passed to it. So something like

    computed: {
      val (flag) {
        return (flag === 1) 
          ? this.someDataProperty * someOtherVariable 
          : this.someDataProperty * 5
        }
    }
    

    can't be done

[EDIT] See: https://vuejs.org/v2/guide/computed.html#Computed-Setter

Watcher

A watcher sample:

watch: {
   val (n, o) {
     console.log(n, o)
   }
}
  1. It does not create any new property, but it watches the changes over a reactive property.

  2. Watches only one specific property, unlike computed where any dependent property change can cause recalculation.

  3. Has arguments of new and old value.


So computed properties would be the way to go if:

You want a property that depends on other properties always. Like text formatting for a template, which is even the example in your code.

Or reducing variable lengths as this is quite common:

this.$store.state.someProperty.someNestedProperty.someDeeplyNestedProperty

can be reduced to:

computed: {
  someDeeplyNestedProperty () {
     return this.$store.state.someProperty.someNestedProperty.someDeeplyNestedProperty
  }
}

Not just reduction in variable size, each time the store updates, you will have the latest value in the someDeeplyNestedProperty.


And Watchers are useful if you want to see if one reactive property has changed to a favourable value to know that you're ready to perform an action.

like:

watch: {
  somethingSelected() {
    this.router.push('someOtherRoute')
  }
}
查看更多
Emotional °昔
4楼-- · 2019-06-14 21:33

For the purpose of this example, computed properties are indeed better. In the example that utilizes watchers notice that this line of code:

this.fullName = this.firstName + ' ' + val

is very similar to this:

this.fullName = val + ' ' + this.lastName

Both serve the same purpose, they are watching for changes in the first or last name and update fullName accordingly. But since this will never change and fullName will always be composed by firstName and lastName then we can avoid the fuss and create a computed property. Then every time firstName and lastName change, fullName will be updated automatically.

There are some cases where using watchers is better though. When you want to do some serious computation of write some async code then a watcher might be more suitable.

For example, if you had something like the following:

let app = new Vue({
    el: '#app',
    data: {
        name: ""
    }
});

And you want, every time that name changes, to make an API call with it, get the result and process it, then a watcher is more appropriate:

watchers: {
    "name": function(newValue, oldValue){
         if(newValue != oldValue)} {
            fetch(url, {method: 'post', body: JSON.stringify({name: this.name})}).then(...);
        }
    }
}

To do that with a computed property you would have to implement a computed get() and a computed set() property that would result in more code.

Also notice that in the documentation's example we have a property, fullName that is composed a.k.a computed by two other properties. In my example name is not computed, in the literal sense of the term. We just want to observe it, so using a computed property would be more of a hack instead of a design pattern.

查看更多
淡お忘
5楼-- · 2019-06-14 21:35

watch

Use watch when you want to perform asynchronous or expensive operations in response to changing data.

computed

Use computed in other cases. The computed properties are cached based on their dependencies. Mostly used when you want to only re-evaluate some of its dependencies have changed.

查看更多
劳资没心,怎么记你
6楼-- · 2019-06-14 21:38

You use a watcher when you want to mutate a value or perform an action based on some other value changing. A good example of this is when you set a value based on a prop and you want to react to any changes:

Vue.component('my-comp',{
  template: '#my-comp',
  props: ['username'],
  created() {
    this.user = this.username;
  },
  watch:{
    username(val){
      this.user = val;
    }
  },
  data(){
    return{
      user: ''
    }
  }
});

See this JSFiddle: https://jsfiddle.net/fjdjq7a8/

That example is a bit contrived and doesn't really work in the real world because we aren't syncing values, so here's a real example where I am using this in one of my open source projects:

Computeds are for arbitrarily manipulating the data itself, so things like concatenating strings and calculating values.

查看更多
祖国的老花朵
7楼-- · 2019-06-14 21:45

Computed properties have a a very specific purpose: composing new data derived from other data. They are used whenever you have some data and need to transform it, filter it, or otherwise manipulate it before using it in the template.

Computed properties always have to return a value, should not have any side effects, and they have to be synchronous.

So there are quite some situations where computed properties won't help you, for example: your component receives a prop, and whenever the prop changes, your component had to make an ajax request. For this, you would need a watcher.

Watchers are not useful as often as computed properties, so you should always think about whether or not a computed property can solve your problem, and only fall back on a watcher (or sometimes a method) if that is not the case.

查看更多
登录 后发表回答