performance secrets revealed

9

Guillaume Chau

@Akryum

Vue.js Core Team

Agenda

Improving the app performance

Better User Experience

Functional components

<template functional>
  <div class="cell">
    <div v-if="props.value" class="on"></div>
    <section v-else class="off"></section>
  </div>
</template>

<script>
export default {
  props: ['value']
}
</script>

Optimized

Unoptimized

<template>
  <div class="cell">
    <div v-if="value" class="on"></div>
    <section v-else class="off"></section>
  </div>
</template>

<script>
export default {
  props: ['value']
}
</script>

Child component splitting

<template>
  <div :style="{ opacity: number / 300 }">
    <ChildComp/>
  </div>
</template>

<script>
export default {
  props: ['number'],
  components: {
    ChildComp: {
      methods: {
        heavy () { /* HEAVY TASK */ }
      },
      render (h) {
        return h('div', this.heavy())
      }
    }
  }
}
</script>

Optimized

Unoptimized

<template>
  <div :style="{ opacity: number / 300 }">
    <div>{{ heavy() }}</div>
  </div>
</template>

<script>
export default {
  props: ['number'],
  methods: {
    heavy () { /* HEAVY TASK */ }
  }
}
</script>

Local variables

<template>
  <div :style="{ opacity: start / 300 }">
    {{ result }}</div>
</template>

<script>
import { heavy } from '@/utils'

export default {
  props: ['start'],
  computed: {
    base () { return 42 },
    result () {
      const base = this.base
      let result = this.start
      for (let i = 0; i < 1000; i++) {
        result += heavy(base)
      }
      return result
    }
  }
}
</script>

Optimized

Unoptimized

<template>
  <div :style="{ opacity: start / 300 }">{{ result }}</div>
</template>

<script>
import { heavy } from '@/utils'

export default {
  props: ['start'],
  computed: {
    base () { return 42 },
    result () {
      let result = this.start
      for (let i = 0; i < 1000; i++) {
        result += heavy(this.base)
      }
      return result
    }
  }
}
</script>

Reuse DOM with v-show

<template functional>
  <div class="cell">
    <div v-show="props.value" class="on">
      <Heavy :n="10000"/>
    </div>
    <section v-show="!props.value" class="off">
      <Heavy :n="10000"/>
    </section>
  </div>
</template>

Optimized

Unoptimized

<template functional>
  <div class="cell">
    <div v-if="props.value" class="on">
      <Heavy :n="10000"/>
    </div>
    <section v-else class="off">
      <Heavy :n="10000"/>
    </section>
  </div>
</template>

(More memory intensive)

Keep-alive

<template>
  <div id="app">
    <keep-alive>
      <router-view/>
    </keep-alive>
  </div>
</template>

Optimized

Unoptimized

<template>
  <div id="app">
    <router-view/>
  </div>
</template>

(More memory intensive)

Deferred features

<template>
  <div>
    <h2>I'm an heavy page</h2>

    <template v-if="defer(2)">
      <Heavy v-for="n in 10" :key="n"/>
    </template>

    <Heavy v-if="defer(3)" class="super-heavy" :n="9999999"/>
  </div>
</template>

<script>
import Defer from '@/mixins/Defer'

export default {
  mixins: [
    Defer()
  ]
}
</script>

Optimized

Unoptimized

<template>
  <div>
    <h2>I'm an heavy page</h2>

    <Heavy v-for="n in 10" :key="n"/>

    <Heavy class="super-heavy" :n="9999999"/>
  </div>
</template>

Perceived performance!

export default function (count = 10) {
  return {
    data () {
      return {
        displayPriority: 0
      }
    },

    mounted () {
      this.runDisplayPriority()
    },

    methods: {
      runDisplayPriority () {
        const step = () => {
          requestAnimationFrame(() => {
            this.displayPriority++
            if (this.displayPriority < count) {
              step()
            }
          })
        }
        step()
      },

      defer (priority) {
        return this.displayPriority >= priority
      }
    }
  }
}

Defer mixin

Time slicing

fetchItems ({ commit }, { items, splitCount }) {
  commit('clearItems')
  const queue = new JobQueue()
  splitArray(items, splitCount).forEach(
    chunk => queue.addJob(done => {
      // Commit array chunks on several frames
      requestAnimationFrame(() => {
        commit('addItems', chunk)
        done()
      })
    })
  )
  // Start and wait for all the jobs
  // to finish
  await queue.start()
}

Optimized

Unoptimized

fetchItems ({ commit }, { items }) {
  commit('clearItems')
  commit('addItems', items)
}

Interaction blocked

Split count: 1000

Split count: 100

Non-reactive data

const data = items.map(
  item => optimizeItem(item)
)

function optimizeItem (item) {
  const itemData = {
    id: uid++,
    vote: 0
  }
  Object.defineProperty(itemData, 'data', {
    // Mark as non-reactive
    configurable: false,
    value: item
  })
  return itemData
}

Optimized

Unoptimized

const data = items.map(
  item => ({
    id: uid++,
    data: item,
    vote: 0
  })
)

(ms for 10,000 items)

x17 faster

Virtual scrolling

<recycle-scroller
  class="items"
  :items="items"
  :item-size="24"
>
  <template v-slot="{ item }">
    <FetchItemView
      :item="item"
      @vote="voteItem(item)"
    />
  </template>
</recycle-scroller>

Optimized

Unoptimized

<div class="items no-v">
  <FetchItemViewFunctional
    v-for="item of items"
    :key="item.id"
    :item="item"
    @vote="voteItem(item)"
  />
</div>

Toggling scroller for 1000 items

Virtual scrolling

<recycle-scroller
  class="items"
  :items="items"
  :item-size="24"
>
  <template v-slot="{ item }">
    <FetchItemView
      :item="item"
      @vote="voteItem(item)"
    />
  </template>
</recycle-scroller>

Optimized

Unoptimized

<div class="items no-v">
  <FetchItemView
    v-for="item of items"
    :key="item.id"
    :item="item"
    @vote="voteItem(item)"
  />
</div>

Crashed

10,000 items

Checkout the demos!

Virtual scrolling

Silver sponsor

Bronze sponsor

Guillaume Chau

@Akryum

github.com/Akryum

Thank you!