[Vuejs]-VueJS, Vuetify, data-table – expandable, performance problem

1👍

This excellent article suggests that the raw number of DOM nodes has the biggest impact on performance. That said, I didn’t experience any real performance bottlenecks in the sample app that I built to learn more about your problem. The entire page with the table loaded in about 1.25s (from localhost), regardless of whether it was in dev mode or it was a production build. The JavaScript console timer reported that expanding or contracting ALL 100 rows simultaneously only took an average of about 0.3s. Bottom line, I think you can achieve the optimizations you’re looking for and not have to give up the conveniences of Vuetify.

Recommendations

  1. Consider displaying fewer rows at one time (biggest expected impact)
  2. Streamline your template to use as few elements as possible, only display data that’s really necessary to users. Do you really need a v-data-table inside a v-data-table?
  3. Streamline your data model and only retrieve the bare minimum data you need to display the table. As @Codeply-er suggested, the size and complexity of your data could be causing this strain

Testing Method

Here’s what I did. I created a simple Vue/Vuetify app with a VDataTable with 100 expandable rows. (The data was pulled from the random user API). I used this method to count DOM nodes. Here are some of the parameters/info:

  • Rows: 100
  • Columns: 5 + the expansion toggler
  • Expansion row content: a VSimpleTable with the user’s picture and address
  • Size of a single JSON record returned from the API: ~62 lines (about half the size of your sample object above)
  • Vue v2.6.11
  • Vuetify v2.3.0-beta.0
    (I realize this just came out, but I don’t think you’d have different results using v2.2.x)
  • App was built with vue create myapp and vue add vuetify
  • VDataTable actually adds/removes the expansion rows from the DOM whenever the rows are expanded/contracted

Here’s some approximate stats on the result (these numbers fluctuated slightly in different conditions–YMMV):

  • 773 (~7/row): number of DOM nodes in 100 rows/5 columns without expansion enabled
  • 977 (+2/row): number of nodes with expansion enabled
  • 24: number of nodes added to the table by expanding a single row
  • 3378 (+26/row): total nodes with ALL rows expanded
  • ~1.25s to load the entire page on a hard refresh
  • ~0.3s to expand or contract ALL of the nodes simultaneously
  • Sorting the columns with the built-in sorting tools was fast and very usable

Here’s code of the App.vue page of my app. The v-data-table almost the only component on the page (except the toggle button) and I didn’t import any external components.

<template>
  <v-app>
    <v-btn
      color="primary"
      @click="toggleExpansion"
    >
      Toggle Expand All
    </v-btn>
    <v-data-table
      :expanded.sync="expanded"
      :headers="headers"
      :items="items"
      item-key="login.uuid"
      :items-per-page="100"
      show-expand
    >
      <template #item.name="{ value: name }">
        {{ name.first }} {{ name.last }}
      </template>
      <template #expanded-item="{ headers, item: person }">
        <td :colspan="headers.length">
          <v-card
            class="ma-2"
            max-width="500px"
          >
            <v-row>
              <v-col cols="4">
                <v-img
                  :aspect-ratio="1"
                  contain
                  :src="person.picture.thumbnail"
                />
              </v-col>
              <v-col cols="8">
                <v-simple-table>
                  <template #default>
                    <tbody>
                      <tr>
                        <th>Name</th>
                        <td class="text-capitalize">
                          {{ person.name.title }}. {{ person.name.first }} {{ person.name.last }}
                        </td>
                      </tr>
                      <tr>
                        <th>Address</th>
                        <td class="text-capitalize">
                          {{ person.location.street.number }} {{ person.location.street.name }}<br>
                          {{ person.location.city }}, {{ person.location.state }} {{ person.location.postcode }}
                        </td>
                      </tr>
                      <tr>
                        <th>DOB</th>
                        <td>
                          {{ (new Date(person.dob.date)).toLocaleDateString() }} (age {{ person.dob.age }})
                        </td>
                      </tr>
                    </tbody>
                  </template>
                </v-simple-table>
              </v-col>
            </v-row>
          </v-card>
        </td>
      </template>
    </v-data-table>
  </v-app>
</template>

<script>
  import axios from 'axios'
  export default {
    name: 'App',
    data: () => ({
      expanded: [],
      headers: [
        { text: 'Name', value: 'name' },
        { text: 'Gender', value: 'gender' },
        { text: 'Phone', value: 'phone' },
        { text: 'Cell', value: 'cell' },
        { text: 'Country', value: 'nat' },
        { text: '', value: 'data-table-expand' },
      ],
      items: [],
    }),
    created () {
      axios.get('https://randomuser.me/api/?seed=stackoverflow&results=100')
        .then(response => {
          this.items = response.data.results
        })
    },
    methods: {
      toggleExpansion () {
        console.time('expansion toggle')
        this.expanded = this.expanded.length ? [] : this.items
        console.timeEnd('expansion toggle')
      },
    },
  }
</script>

You can see a working demo in this codeply. Hope this helps!

Leave a comment