Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Are methods in Vue reactive?

Tags:

vue.js

I've been using Vue for a while, and my experience has always been a method will recompute if its underlying reactive data is updated. I've encountered conflicting information on SO:

  • I was attempting to answer this question, and was told multiple times that this isn't the case.
  • The accepted answer here indicates that "[a method] will only be evaluated when you explicitly call it."

I searched through the docs and I didn't see anything incredibly clear.

If they are not reactive, then why does this example work?

<ul>
  <li v-for="animal in animals" :key="animal.id">
    <span v-if="isAwesome(animal)">{{ animal.name }}</span>
  </li>
</ul>
export default {
  data() {
    return {
      awesomeAnimalIds: [],
      animals: [
        { id: 1, name: 'dog' },
        { id: 5, name: 'cat' },
        { id: 9, name: 'fish' },
      ],
    };
  },
  created() {
    setTimeout(() => {
      this.awesomeAnimalIds.push(5);
    }, 1000);
    setTimeout(() => {
      this.awesomeAnimalIds.push(9);
    }, 2000);
  },
  methods: {
    isAwesome(animal) {
      return this.awesomeAnimalIds.includes(animal.id);
    },
  },
};

I would really like to have a definitive and satisfying answer that this community can refer to.

like image 674
David Weldon Avatar asked Jan 07 '20 21:01

David Weldon


1 Answers

Based on How Changes Are Tracked from the docs, here's what's going on:

Vue reactivity cycle diagram

  1. A special watcher is created for the component instance to determine when a re-render is required.

  2. Vue converts all properties of data to getters and setters.

get animals() {
  // Add dependency: potentially trigger a re-render if animals updates
  ...
}
set animals() {
  // Notify the watcher that animals has been updated
  ...
}
get awesomeAnimalIds() {
  // Add dependency: potentially trigger a re-render if awesomeAnimalIds updates
  ...
}
set awesomeAnimalIds() {
  // Notify the watcher that awesomeAnimalIds has been updated
  ...
}

  1. The template is rendered. During the render, a call to isAwesome is made from the template.
  2. In the body of isAwesome, the getter for awesomeAnimalIds is invoked.
  3. The watcher establishes a dependency on the awesomeAnimalIds field of data.
  4. After a timeout, awesomeAnimalIds is updated, which invokes the awesomeAnimalIds setter.
  5. Because the template depends on a data field which received a notification, a re-render is triggered.
  6. Repeat step (3).

From this and the example above, we can conclude the following:

A method call made from a template establishes a reactive dependency on the subset of data fields used in the method call stack. If the underlying fields are updated, it will trigger a re-render of the component.

There is a common misconception that methods are "invoked only once" or "fire and forget" when called from a template. This is clearly not always the case because methods can establish a reactive dependency.

So when should we use a computed property vs a method?

See the guide section on Computed Caching vs Methods. Here's my take on it:

  • A computed property will only reevaluate when its reactive dependencies have changed. I.e. it uses caching for improved efficiency.
  • Computed properties should be side-effect free. E.g. you shouldn't call fetch from them.
  • Always prefer a computed property to a method if possible for efficiency reasons.
  • Use a method if you have side effects or if you need to pass in an argument (as seen in the original question).
like image 168
David Weldon Avatar answered Sep 23 '22 21:09

David Weldon