'Are methods in Vue reactive?

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.



Solution 1:[1]

No, methods are not reactive. Only data can be reactive in Vue.

BUT its is important understand how Vue works...

  1. Vue will take your template, compiles it into render function and runs the render function
  2. While the render function is running, Vue monitors all data() members (it does this all the time, not only during 1st render). If any of the data is accessed during render, Vue knows that content of this data member influences the result of rendering.
  3. Vue use the knowledge gathered in step 2. Whenever data member "touched" during rendering changes, it knows re-render should happen and do the thing...

It doesn't matter if you reference the data member directly, use it in computed or in a method. If the data is "touched" during rendering, the change of the data will trigger re-render in the future...

Solution 2:[2]

This is a very interesting case.

From what I have read and my experience I can say that: No, methods are not inherently reactive. A method must be explicitly called for it to execute.

But, then how can I explain your case? I put your code in in a sandbox and sure enough, as you push id's into the array, the template updates to display the animal name. This would indicate some reactivity. What gives?

Well, I ran an experiment. I added a simple div to each loop that generates a random number when generated.

<li v-for="animal in animals" :key="animal.id">
        <div>{{ random() }}</div>
        <span v-if="isAwesome(animal)">{{ animal.name }}</span>
</li>

...

random() {
      return Math.random();
}

And what I saw was that every time a new id was pushed into the array, all the random numbers would change. This is the key to understand why it "seems" as though the method isAwesome is reactive.

Somehow, when a new ID is pushed to the array, Vue re-renders the loop entirely, hence executing the methods again. I can't explain the inner workings of why vue re-renders the entire loop, that would require further research.

So to answer your question. isAwesome is not reactive, it is merely an illusion created by the re-render of the loop.

Solution 3:[3]

To summarize

  • Computed properties are cached based on their reactive dependencies. A computed property will only re-evaluate when some of its reactive dependencies have changed.
  • Methods will always run whenever a re-render happens. So you need to be careful not putting too heavy logic in methods and use computed properties instead when possible.

Source: https://v3.vuejs.org/guide/computed.html#computed-caching-vs-methods

See it in action

https://codepen.io/hulius/pen/BamQaWQ

<div id="app">
  <textarea v-model="notADependency"> </textarea>
  <textarea v-model="dependency"> </textarea>
  <p>
    method (run at each render):
    <pre>{{ methodNow() }}</pre>
  </p>
  <p>
    computed (run only when a dependency is changed):
    <pre>{{ computedNow }}</pre>
  </p>
</div>
const app = Vue.createApp({
  data() {
    return {
      notADependency: "I'm not a dependency, I will only trigger a new rendering",
      dependency: "I'm a dependency, all computed properties depending on me will be recomputed, then it will trigger a new rendering"
    }
  },
  computed: {
    computedNow() {
      return Date.now() + " " + this.dependency
    }
  },
  methods: {
    methodNow() {
      return Date.now() + " " + this.dependency
    }
  }
})

app.mount('#app')

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2
Solution 3 hulius