When to avoid VueJs Computed Properties for greater performance
Tags: vue, javascript, webperf
Published: 2020-08-14 17:11:24

VueJs is fast, easy to learn, reactive framework. It offers a gentle learning curve and a rich developer experience, with powerful inspection and CLI based tools.

At [modo](https://modo.md), we use Vue for most of our dynamic frontend projects: we like it for its clear separation of concerns, easily composable components, a wide range of build options (from Single Page Applications to standalone Web Components) and for being generally very well performing out–of–the–box.

### Quick links
  * [Computed Properties](#ch1)
  * [What happens inside computed properties](#ch2)
  * [How and when to avoid computed properties](#ch3)
  * [Going from a few milliseconds to tens of seconds of execution (and back) ](#ch4)
  * [Performance by design](#made-by-modo)

## Computed Properties <a name="ch1"></a>

Among the many traits of VueJs that are optimized for computational and rendering performance are **computed properties**. Computed properties are component functions that return a value. They are reactive, in that they are automatically updated whenever one of  the values used for computing the property changes, and can be used in templates and component logic.

The real power (and the true difference in comparison to component methods) is that computed properties are cached.

While component methods are executed at every rendering of the component, computed properties are evaluated conditionally: if none of the dependency has changed, the property is not recomputed and the cache value is used, saving a considerable amount of runtime execution. Component re–rendering thus happens considerably faster, as the runtime engine can skip re–evaluating potentially expensive data, relying instead on the cached value.

If you’re not familiar with computed properties, you can read more in the [official documentation](https://vuejs.org/v2/guide/computed.html#Computed-Properties), but for the sake of this article a simple use case can help.

Suppose we have a list of books, and a component that should filter them based on a condition, let’s say a matching text in the title.

Our code would probably look like this:

```vue
<template>
  <section class="c-book">
    <h2>Your search for {{ bookQuery }} returned {{ filteredBookList.length }} books</h2>
    <ul v-if="filteredBookList.length" class="c-book__list">
      <li v-for="book in filteredBookList" :key="book.id">
        {{ book.title }}
      </li>
    </ul>
  </section>
</template>
<script>
export default {
  name: 'BookFilter',

  props: {
    bookList: {
      type: Array,
      required: true,
    },
    bookQuery: {
      type: String,
      default: '',
    },
  },

  computed: {
    filteredBookList () {
      return this.bookList.filter(book => book.title.includes(this.bookQuery))
    },
  },
}
</script>
```

Our component and application would probably have additional logic and greater complexity, but this example should be good enough to show how computed properties work. `filteredBookList` returns a copy of the array of books, filtered with those that include the search query (`bookQuery`) in their title. The value is then cached by Vue, and will not be recomputed unless either `bookList` or `bookQuery` change.

An important tidbit to remember, tightly coupled with their _cacheability_, is that **computed properties must be evaluated synchronously**.

## What happens inside computed properties <a name="ch2"></a>

We’re not going to deep dive into Vue internals. If you are interested, VueMastery has produced a series of videos with Evan You coding [step–by–step, high level, simplified demos of how VueJs works](https://www.vuemastery.com/courses/vue3-deep-dive-with-evan-you/vue3-overview/). You can also inspect [Vue source code](https://github.com/vuejs/vue) and, for this specific case, I found [this article by Michael Gallagher](https://medium.com/dailyjs/tracing-or-debugging-vue-js-reactivity-the-computed-tree-9da0ba1df5f9) very inspiring. 

So, to make it short, in version 2.x, [Vue tracks reactivity using _getters_ and _setters_](https://vuejs.org/v2/guide/reactivity.html#How-Changes-Are-Tracked) ([Version 3 will use _proxies_](https://medium.com/the-vue-point/plans-for-the-next-iteration-of-vue-js-777ffea6fabf#ba44), and will also provide better tracing and debugging of reactivity. It is currently a Release Candidate).

To understand why computed properties can bite back in specific cases, we need to remember that Vue needs to track each dependency of a computed property. This can be expensive if these variables are large arrays, or complex, nested objects, or a combination of both, as in the case I found out. 

In case of circular dependencies (a computed property depending on another computed property), Vue also needs to stabilize values and might have to [iterate the evaluation twice](https://github.com/vuejs/vue/issues/622#issuecomment-66470072) to ensure that values don’t change within the current update cycle.

All of this can add up significantly.

## How and when to avoid computed properties <a name="ch3"></a>

Despite all the praises I’ve written so far for computed properties, there are cases in which you might want or even _need_ to avoid them.

The simplest case is if you need a _static_ computed value, that is a value that needs to be calculated only once, no matter how data in your component will change.

The _options API_ of VueJs 2.x does not make it particularly evident, but declaring any property inside the component _created_ function makes it available to both the component logic and the component template.

Let’s see this in code, with our book example:

```vue
<template>
  <section class="c-book">
    <h2>Your search for {{ bookQuery }} returned {{ filteredBookList.length }} books</h2>
    <ul v-if="filteredBookList.length" class="c-book__list">
      <li v-for="book in filteredBookList" :key="book.id">
        {{ book.title }}
      </li>
    </ul>
  <footer>
    {{ productName }} v{{ productVersion }}
  </footer>
  </section>
</template>
<script>
// let’s import the name and version number from package.json
import { version: productVersion, name: productName } from "../package.json";

export default {
  name: 'BookFilter',

  props: {
    bookList: {
      type: Array,
      required: true,
    },
    bookQuery: {
      type: String,
      default: '',
    },
  },

  computed: {
    filteredBookList () {
      return this.bookList.filter(book => book.title.includes(this.bookQuery))
    },
  },

  created() {
    // once the component has been created, we can add non reactive properties to it.
    // this data is static and will never be tracked by Vue
    this.productVersion = productVersion;
    this.productName = productName;
  }
}
</script>
```
<small>[See this example on CodeSandbox](https://codesandbox.io/s/book-search-vue-demo-pk4hh?file=/src/App.vue)</small>

In the above example, the product name and version are completely static. They are imported from the `package.json` file and used in the template as if they were any other computed or reactive property.

You could still define `productVersion` and `productName` as computed properties: Vue would not track values that are not registered in the reactive system, but this approach becomes useful when you need to explicitely avoid having Vue track a large array or object.

In our example above, `bookList` is reactive. It is passed to the component as a prop, and therefore tracked for reactivity changes. The component needs to update should the list change. If the list is very large and includes complex objects, we’re adding an unnecessary overhead to the reactivity system. We’re not reacting to changes in `bookList`: we’re reacting only to changes in `bookQuery`! The list of books stays the same, no matter the search the user performs.

This might not be intuitive, and most of the time Vue is forgiving because its internal optimizations favour speed. But every time we know that some data does not need to ever be re–evaluated, we should **design for performance** and make it available as a static property.

<small>[See the final example on CodeSandbox](https://codesandbox.io/s/book-search-vue-demo-static-list-qcxeh?file=/src/main.js)</small>

## Going from a few milliseconds to tens of seconds of execution (and back) <a name="ch4"></a>

The book example described above has many similarities with a project I worked on recently. This project needed to handle an array of around 10.000 objects, and filter them dynamically based on various query types.

In a particular scenario, the performance of the app degraded significantly, blocking the browser main thread for several seconds and making the browser completely unresponsive. Granted, the app was managing a lot of data: the JSON representation of the array was ~19Mb of data. But the slowdown was not due to the data size.

The code for filtering the array was not at fault. An isolated case in plain JavaScript performing the same filtering took only a few milliseconds, no matter the code style (functional or imperative) or the complexity of the filter function.

> the greatest advantage of a computed property can sometime become its greatest defect

To troubleshoot the issue, I used the performance profiler of Firefox DevTools to generate a [flame chart](https://developer.mozilla.org/en-US/docs/Tools/Performance/Flame_Chart). This showed where the application was spending time in long tasks that blocked the browser main thread. Reading the chart provided some insight: it turned out that the greatest advantage of computed properties sometimes becomes a performance bottleneck. 

![A flame chart from Firefox DevTools](https://media.prod.mdn.mozit.cloud/attachments/2015/05/29/10989/49567ba0c0d3027e27a9daed10364417/perf-flame-chart-overview.png)

Tracking dependencies has a cost. Most of the time this cost is negligible, especially compared to the advantage provided by cached values and a fast, reactive frontend. In our app, this cost was acceptable when the component tracked in total one list of items. But when tracking two, and combining this cost with the cost generated on the heap and the call stack by a high number of callback functions—such as the one used to `filter()` a large array—it blocked the browser event loop and made the application unresponsive.

To solve it, as explained above, we removed the list of items to search from the reactivity system. In retrospect it was a mistake to add it from the beginning, since it never changes and never needs to be tracked for updates. The array is now filtered once inside the `created()` function and the resulting value added as a non–reactive property to the component. We’re back in the milliseconds range of updates. Yeah! 🍾

## Performance by design <a name="made-by-modo"></a>

Despite working with VueJs for years, this was the first time we hit a performance penalty this severe. VueJs 3.x has a lot of internal improvements and might perform flawlessly in this scenario (we haven’t tested it yet). But if you’re still relying on VueJs 2, and like us are obsessed with performance, I hope this helps you if you ever experience an unexpected slowdown in your VueJs code. And if it’s really puzzling, **you might want to let us take a look at it**.

<small><span>Cover photo by <a href="https://unsplash.com/@heyeje?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Heye Jensen</a> on <a href="https://unsplash.com/s/photos/u-turn?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a></span></small>