computed

Computed is a computing property that dynamically displays the latest computed results based on the dependent responsive property. The results of evaluated properties are cached and are recalculated only if the dependent responsive property changes. Although a computed property is written formally as a method, it is eventually mixed into the Vue instance for use as a property, and the this context of all getters and setters is automatically bound to the Vue instance as follows:

const vm = new Vue({
  // ...
  data: {
    n: 1
  },
  computed: {
    // Use 1: only reads and evaluates getters for properties
    double () {
      // this points to the current instance VM
      return this.n * 2
    },
    Getters and setters of properties are read and set. Getters and setters of properties are computed
    plus: {
      // getter
      get () {
        return this.n + 1
      },
      // setter
      set (value) {
        // this points to the current instance VM
        this.n = value - 1}}}})console.log(vm.double) / / 2
vm.plus = 5 
console.log(vm.n) // n => 4
console.log(vm.plus) / / 5
console.log(vm.double) / / 8
Copy the code

Use scenarios for computed: When the required value changes based on the property changes in the data object, as in the following two examples:

Displaying User Information

When developing a business, user information needs to be displayed in the order of nickname, mobile phone, and email somewhere on the page, with the following rules:

  • Show the phone without the nickname.
  • Display email without a nickname or phone.

At the same time, the user information presentation module will use many pages. We can quickly complete a business requirement by using the double-curly interpolation method:

<template> <! <p> User information: {{ user.nickname || user.phone || user.email }}</p> </template> <script> export default { data () { return { user: {nickname: 'nickname ', phone: '13389896666', email: '[email protected]'}}}} </script>Copy the code

Here are the results:

However, if one day the business changes and the display order of user information needs to be changed to mobile phone, nickname and email, then the code of the module containing user information in our page needs to be changed, which is extremely inconvenient. At the same time, the long string of expressions in double curly braces is not simple declarative logic, and you need to see the logic before you modify the service. For this reason, we can use computed to optimize:

<template>
  <! -- Use computed properties to display user information -->
  <p>User information: {{displayUser}}</p>
</template>

<script>
export default {
  // ...
  computed: {
    displayUser () {
      const { user: { phone, nickname, email }} = this
      // Change the display order of user information to mobile phone, nickname and email
      return phone || nickname || email
    }
  }
}
</script>
Copy the code

The previous long expression, which was less than concise in the double curly braces, was replaced with a computed property, displayUser, so that the logic inside the template looks clear.

We can use a computed property in a template just like we would use a normal data property. Vue knows that the computed property displayUser depends on the User property, and that when the user property changes (for example, Changes in the user information, changes in the order in which the user information is displayed, etc.), where the computed displayUser property is used in the template, it is automatically updated.

If the page also has a change phone number button, which resets the user’s phone number when clicked, then the computed property could be rewritten as:

<script> export default { // ... Computed: {// Set and read displayUser: {get () {const {user: { phone, nickname, email }} = this return phone || nickname || email }, set (value) { this.user.phone = value } } }, methods: { setPhone () { this.displayUser = '15133339999' } } } </script>Copy the code

So when the modify phone number button is clicked, the setter that calculates the displayUser property will be called, updating the value of user.phone property.

Switch user lists based on gender

There is a list of user nicknames and gender on the page:

<template> <div> <div> <div> <ul> <li v-for="user in users" > {{user.nickname}} - {{user.gender}} </li> </ul> </div> </template> <script  createUser = (nickname, gender) => { id += 1 return { id, nickname: nickname, gender: gender } } export default { data: { users: [createUser (' road 'and' male '), createUser (' na beauty ', 'woman'), createUser (' sauron 'and' male '), createUser (' mountain ', 'male'), createUser (' robin ', </script>Copy the code

Here are the results:

The requirement to achieve is to switch the user list according to the different buttons clicked. The specific rules are as follows:

  • All users are displayed when the all button is clicked.
  • Click the “male” button to display the user with male gender.
  • Click the female button to display the user with female gender.

The data source for the user list is changed to calculate the property displayUsers, which displays the corresponding user based on the gender Property value:

<template> <div> <div> <! < button@click ="setGender('all')"> </button> < button@click ="setGender('male')"> </button> <button @ click = "setGender (' female ')" > women < / button > < / div > < ul > <! <li v-for="user in displayUsers" :key="user.id"> {{user.nickname}} - {{ user.gender }} </li> </ul> </div> </template> <script> // ... export default { data: { // ... Gender: 'all'}, computed: {displayUsers () {const genderHash = {male: 'male ', female: } const {users, gender} = this return gender === 'all'? users : users.filter(user => user.gender === genderHash[gender]) } }, methods: { setGender (gender) { this.gender = gender } } } </script>Copy the code

Final effect:

The above two examples have given you more insight into the use of computed attributes, which make your templates cleaner, your logic clearer, and your code more maintainable.

computed vs methods

In the example of changing the user list by gender, we can also use methods to accomplish the same requirement. The idea is as follows:

  • First the user lists the data sourceusersProperty cannot be modified, so add onedisplayUsersThe property.
  • increatedThe hook,usersAssigned todisplayUsers.
  • Add a way to show different users by gendershowUsers.

The complete code is as follows:

<script> // ... Export default {data: {// add displayUsers displayUsers: []}, created () {// assign the value this.displayUsers = this.users} to the created hook, methods: {// Add the showUsers method showUsers (gender) {const genderHash = {male: 'male ', female: } const {users} = this this.displayUsers === 'all'? users : users.filter(user => user.gender === genderHash[gender]) }, setGender (gender) { this.showUsers(gender) } } } </script>Copy the code

From the example, we can see that both the usage method and the computed attributes fulfill the requirements, but the difference is that the computed attributes can be cached based on their responsive dependencies and are recalculated only when the relevant dependencies change; Calling a method always reexecutes the function.

  • When using the method implementation, add a log to the showUsers method:

    <script> export default { methods: { showUsers (gender) { // ... Console. log(' displayUsers evaluates ${genderHash[gender]} once ')}}} </script>Copy the code

    When clicking the button:

    The showUsers method is recalled whether different buttons are clicked or the same button is clicked consecutively, indicating that the method is always reexecuted and the results are not cached.

  • When using a computed property implementation, add a log to the computed property displayUsers:

    <script> export default { // ... computed: { displayUsers () { // ... Console. log(' displayUsers evaluates ${genderHash[gender]} once ')}}} </script>Copy the code

    When clicking the button:

When you click on different buttons, displayUsers will evaluate immediately because of the gender change; But when the same button is clicked consecutively, gender does not change, and displayUsers immediately returns the previous calculation without re-evaluating it. This indicates that the computed property, displayUsers, is computed based on the gender Property it depends on and that the result is cached.

The benefit of caching is that when there is a scenario that requires a lot of calculation, which leads to a large cost of page performance, using the computing attribute can reduce the cost of the page by using the caching feature. Using methods, however, will re-perform the calculation each time, which may affect the user experience.

watch

Watch is a listener. You can perform some operations by watching the property value change in the data object. Watch is an asynchronous process. The use of watch is an object, the key is the expression to be observed, and the value is the corresponding callback function. The representation of the value can be a function, a method name, an object containing options, or an array containing callbacks:

new Vue({
  data: {
    a: 1.b: 2.c: {
      c1: '111'.c2: '222'
    },
    d: 4.e: 5.user: {
      nickname: 'luffy'.age: 18}},watch: {
    // In the first case, the value is a function
    a (newValue, oldValue) {
      console.log(`from: ${oldValue}, to: ${newValue}`)},// Second, the value is the method in the methods
    b: 'methodName'.// The third type is an object with options such as deep and immediate, and the callback function is handler
    c: {
      handler (newValue, oldValue) {},
      // deep: true means that the listener c will call the handler callback whenever a property changes, no matter how deep the property is nested
      deep: true
    },
    d: {
      handler: 'methodName'.// Watch does not perform the handler callback initially, but only when the d property changes
      // Setting immediate: true causes the Handler callback to be called initially
      immediate: true
    },
    // Fourth, the value is an array of callbacks that are called one by one
    e: [
      function handler1(newValue, oldValue) {},
      'handler2',
      {
        hanlder (newValue, oldValue) {},
        / / options}].// Key can also be an attribute in an object in data
    'user.nickname' (newValue, oldValue) {}
  }
})
Copy the code

It is important to note that arrow functions should not be used in watch.

What is data change

The watch responds when the value of the property it watches changes. Here’s how the property value changes:

<template> <div> < button@click ="visible =! "</button> < button@click ="visible =! </button> < button@click =" visible 2 = {b: </div> </template> <script> export default {data () {return {n: 1, visible: false, obj1: {a: 'aaa' }, obj2: { b: 'bbb' } } }, watch: {n () {console.log(' click button 1: n changes ')}, visible () {console.log(' click button 2: visible changes ')}, obj1 () {console.log(' Click button 3: visible changes ')}, obj1 () {console.log(' Click button 3: visible changes ')}, obj1 () {console.log(' Click button 3: Obj1 changed ')}, 'obj1.a' () {console.log(' Click button 3: Obj1.a changed ')}, obj2 () {console.log(' Click button 4: }, 'obj2.b' () {console.log(' click button 4: obj2.b changed f')}}} </script>Copy the code

Clicking the button changes the value of some property in the data object,watchWhen it hears these property values change, it prints them out in the console. The result is as follows:Like then,visibleobj1.aProperties of this primitive type are changed whenever their values changewatchListening in.

For properties of complex types such as objects, the watch listens only when the address of the reference value changes. A += ‘GGG’ (obj1.a += ‘GGG’). The value of obj1.a is changed, but the address of the reference value to obj1 is not changed, so it is not listened for.

When obj2 = {b: ‘BBB’} is executed with the click of button 4, the address of obj2’s reference has changed, so it will still be listened for, even though the new object assigned to obj2 has the same key-value pair as before.

To make Watch think obj1 has changed when obj1.a has changed, add deep: true:

/ /... <script> export default { // ... Watch: {obj1: {handler () {console.log(' click button 3: obj1 changed ')}, deep: true}}} </script>Copy the code

When button 3 is clicked, the console prints the following results:

Watch is asynchronous

The page has a module that shows the current value and the operation history:

The requirement is that the current value and the operation record will be recorded and displayed in the page by clicking different addition and subtraction buttons. Click the undo button to restore the current value and operation record to the data of the last operation. The corresponding code is as follows:

<template> <div class="about"> <p> {{ n }}</p> <div> <button @click="n += 2">+2</button> <button @click="n += 5">+5</button> <button @click="n -= 3">-3</button> <button :disabled="history.length === 0" @click="revoke"> </button> < span style = "box-sizing: border-box; color: RGB (74, 74, 74); line-height: 20px; font-size: 13px! Important; white-space: inherit! Important;" [] } }, watch: { n (newValue, oldValue) { this.history.push({ from: oldValue, to: newValue }) } }, methods: { revoke () { const lastItem = this.history.pop() this.n = lastItem.from } } } </script>Copy the code

After clicking the add/subtract button on the page and then clicking the Undo button, you will see that the history has not been restored to the data before the last operation. This is because n is always in the watch state, and the watch will add the assignment of n in the undo operation to the history as a new operation record. The solution isto add a state to determine if n is currently in the undo state:

<script> export default { data () { return { // ... InRevokeStatus: false}}, watch: {n (newValue, oldValue) {// If (! this.inRevokeStatus) { this.history.push({ from: oldValue, to: newValue }) } } }, methods: Revoke () {// After clicking the revoke button, InRevokeStatus = true const lastItem = this.history.pop() this.n = lastitem. from // Set inRevokeStatus to undo status false this.inRevokeStatus = false}}} </script>Copy the code

Update the above code and repeat the previous operation, and you will see that there is still data in history that n was in the undo operation. Verify what the undo state is after clicking the Undo button by typing log:

<script> export default { // ... Watch: {n (newValue, oldValue) {console.log(' Is this in the undo state: ', this.inrevokeStatus? 'in' : 'not in ') if (! this.inRevokeStatus) { this.history.push({ from: oldValue, to: newValue }) } } }, // ... } </script>Copy the code

Here are the results:

Although the undo state is set in the undo operation, the log in watch shows that it is still not in the undo state. This is because watch is an asynchronous process. It will wait for the current code to be executed before operating on the change of n. The general process is as follows:

// Click the Undo button and set the undo status to True
this.inRevokeStatus = true
// Assign a value to n, but watch will not execute immediately, even though n has changed
this.n = lastItem.from
// Set the undo status to false
this.inRevokeStatus = false
// At last, watch will perform the operation of n change, so it will never be in the undo state, so click the undo button, and new operation records will always be added in history
Copy the code

To solve this problem, use $nextTick:

<script> export default { methods: Revoke () {this.inrevokeStatus = true const lastItem = this.history.pop() this.n = lastitem. from // nextTick $nextTick(() => {this.inrevokeStatus = false})}} </script>Copy the code

Here are the results:

watch vs computed

In the example showing user information, you can use Watch to achieve the same effect:

<template> <div class="about"> <p> {{displayUser}}</p> <button @click="user.nickname = '"> </button> </div> </template> <script> export default { Data () {return {user: {nickname: 'nickname ', phone: '13389896666', email: '[email protected]'}, displayUser:'}}, watch: 'user.nickname': {handler () {const {user: {nickname, phone, email}} = this enclosing displayUser = nickname | | phone | | email}, / / the Vue does not think at the start of the data from a process of change, So the page displayUser empty / / immediate: true said data from scratch is the process of change, the page displayUser there will be a nickname | | phone | | email immediate expression of a value: true }, 'user.phone': { handler () { const { user: { nickname, phone, email } } = this this.displayUser = nickname || phone || email }, immediate: true }, 'user.email': { handler () { const { user: { nickname, phone, email } } = this this.displayUser = nickname || phone || email }, immediate: true } } } </script>Copy the code

Compared to computed, the code implemented with Watch is more and more repetitive. Use computed where most computed and Watch are available, and watch if you need to do something when the data changes.

Conclusion:

  • computedIs a computed property that dynamically displays the latest computed result based on the dependent responsive property. The results of evaluated properties are cached and are recalculated only if the dependent responsive property changes. Computed properties are applied to a scenario where data depends on other data properties.
  • watchIt’s a listener. It can passwatchPerform some operations when the property value in the data object changes,watchIs an asynchronous process.watchIs passed in the callbacknewValueoldValueFor use, also providedimmediatedeepThe option to.watchThis is used to perform some operations when the data Property value changes.