performance secrets revealed
![](https://s3.amazonaws.com/media-p.slid.es/uploads/638176/images/3404520/vuejs-logo.png)
9
![](https://s3.amazonaws.com/media-p.slid.es/uploads/638176/images/4796261/avatar_copie.jpg)
Guillaume Chau
![](https://s3.amazonaws.com/media-p.slid.es/uploads/638176/images/3405366/twitterbird_RGB.png)
@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>
![](https://s3.amazonaws.com/media-p.slid.es/uploads/638176/images/5923599/Screenshot_from_2019-03-21_20-13-07.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/638176/images/5923601/Screenshot_from_2019-03-21_20-13-19.png)
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>
![](https://s3.amazonaws.com/media-p.slid.es/uploads/638176/images/5922649/Screenshot_from_2019-03-21_17-05-52.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/638176/images/5922651/Screenshot_from_2019-03-21_17-06-02.png)
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>
![](https://s3.amazonaws.com/media-p.slid.es/uploads/638176/images/5922663/Screenshot_from_2019-03-21_17-06-09.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/638176/images/5922665/Screenshot_from_2019-03-21_17-06-18.png)
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>
![](https://s3.amazonaws.com/media-p.slid.es/uploads/638176/images/5923365/Screenshot_from_2019-03-21_19-12-24.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/638176/images/5923366/Screenshot_from_2019-03-21_19-12-32.png)
(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)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/638176/images/5923529/Screenshot_from_2019-03-21_19-53-30.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/638176/images/5923528/Screenshot_from_2019-03-21_19-53-24.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/638176/images/5923527/Screenshot_from_2019-03-21_19-52-50.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/638176/images/5923525/Screenshot_from_2019-03-21_19-52-38.png)
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>
![](https://s3.amazonaws.com/media-p.slid.es/uploads/638176/images/5923215/Screenshot_from_2019-03-21_18-43-00.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/638176/images/5923216/Screenshot_from_2019-03-21_18-42-50.png)
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
![](https://s3.amazonaws.com/media-p.slid.es/uploads/638176/images/5922799/Screenshot_from_2019-03-21_17-36-44.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/638176/images/5922800/Screenshot_from_2019-03-21_17-37-11.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/638176/images/5922801/Screenshot_from_2019-03-21_17-37-36.png)
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
})
)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/638176/images/5922829/Screenshot_from_2019-03-21_17-44-29.png)
(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>
![](https://s3.amazonaws.com/media-p.slid.es/uploads/638176/images/5922858/Screenshot_from_2019-03-21_17-51-48.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/638176/images/5922859/Screenshot_from_2019-03-21_17-51-57.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/638176/images/5922860/Screenshot_from_2019-03-21_17-52-04.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/638176/images/5922861/Screenshot_from_2019-03-21_17-52-11.png)
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
![](https://s3.amazonaws.com/media-p.slid.es/uploads/638176/images/5922878/Screenshot_from_2019-03-21_17-56-00.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/638176/images/5922879/Screenshot_from_2019-03-21_17-56-09.png)
Checkout the demos!
Virtual scrolling
![](https://s3.amazonaws.com/media-p.slid.es/uploads/638176/images/4989468/become_a_patron_button_3x.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/638176/images/5382472/patreon-screenshot-blur.png)
Silver sponsor
Bronze sponsor
![](https://s3.amazonaws.com/media-p.slid.es/uploads/638176/images/4796261/avatar_copie.jpg)
Guillaume Chau
![](https://s3.amazonaws.com/media-p.slid.es/uploads/638176/images/3405366/twitterbird_RGB.png)
@Akryum
![](https://s3.amazonaws.com/media-p.slid.es/uploads/638176/images/4614879/GitHub-Mark-Light-120px-plus.png)
github.com/Akryum
Thank you!
9 performance secrets revealed
By Guillaume Chau
9 performance secrets revealed
- 28,392