翻到一個star,VUE 效能優化給大家分享一下。
業精於勤,行成於思.
Vue 9 Perf Secrets
栗子
- Functional components
- Child component splitting
- Local variables
- Reuse DOM with v-show
- Keep alive(DOM-Reusing Router View)
- Deferred features
- Vuex demo
原始碼中有個 Static
被註釋,沒有達到作者的優化效果。
吃栗子
栗子中都分別寫在On,Off的對應元件檔案中。對比兩份程式碼。
Functional components
FunctionalOff
<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>
複製程式碼
FunctionalOn
<template functional>
<div class="cell">
<div v-if="props.value" class="on"></div>
<section v-else class="off"></section>
</div>
</template>
複製程式碼
Child component splitting
ChildOff
<template>
<div :style="{ opacity: number / 300 }">
<div>{{ heavy() }}</div>
</div>
</template>
<script>
export default {
props: ['number'],
methods: {
heavy () {
const n = 100000
let result = 0
for (let i = 0; i < n; i++) {
result += Math.sqrt(Math.cos(Math.sin(42)))
}
return result
},
},
}
</script>
複製程式碼
ChildOn
<template>
<div :style="{ opacity: number / 300 }">
<ChildComp/>
</div>
</template>
<script>
export default {
components: {
ChildComp: {
methods: {
heavy () {
const n = 100000
let result = 0
for (let i = 0; i < n; i++) {
result += Math.sqrt(Math.cos(Math.sin(42)))
}
return result
},
},
render (h) {
return h('div', this.heavy())
},
},
},
props: ['number'],
}
</script>
複製程式碼
關於一個Issues
ustbhuangyi (附上原文地址),提出例子中效能問題,用了 heavy
function,應該採用計算屬性 computed。這也是使用中,效能優化可以考慮的點,什麼時候該用計算屬性。
First, thanks for the awesome Project!In the Child component splitting demo,the root cause of the performance problem is that we use heavy function during render, it will also be called when the component render.
Instead of using child component splitting, can we use computed property? For example:
<template> <div :style="{ opacity: number / 300 }"> <div>{{ heavy }}</div> </div> </template> <script> export default { props: ['number'], computed: { heavy () { const n = 100000 let result = 0 for (let i = 0; i < n; i++) { result += Math.sqrt(Math.cos(Math.sin(42))) } return result } } } 複製程式碼
Because create a child component will have some extra overhead, so computed property here is better ? Computed property will use cache if it's dependencies not change, and I think we should use computed property as much as possible other than method, unless in some special cases.
Local variables
LocalOff
<template>
<div :style="{ opacity: start / 300 }">{{ result }}</div>
</template>
<script>
export default {
props: ['start'],
computed: {
base () {
return 42
},
result () {
let result = this.start
for (let i = 0; i < 1000; i++) {
result += Math.sqrt(Math.cos(Math.sin(this.base))) + this.base * this.base + this.base + this.base * 2 + this.base * 3
}
return result
},
},
}
</script>
複製程式碼
LocalOn
<template>
<div :style="{ opacity: start / 300 }">{{ result }}</div>
</template>
<script>
export default {
props: ['start'],
computed: {
base () {
return 42
},
result ({ base, start }) {
let result = start
for (let i = 0; i < 1000; i++) {
result += Math.sqrt(Math.cos(Math.sin(base))) + base * base + base + base * 2 + base * 3
}
return result
},
},
}
</script>
複製程式碼
Reuse DOM with v-show
HideOff
<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>
複製程式碼
HideOn
<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>
複製程式碼
Keep alive (DOM-Reusing Router View)
<template>
<Benchmark title="DOM-Reusing Router View" class="keep-alive">
<template #toolbar>
<VueGroup v-model="page">
<VueGroupButton :value="false">Simple page</VueGroupButton>
<VueGroupButton :value="true">Heavy page</VueGroupButton>
</VueGroup>
<PlayToggle v-model="play"/>
</template>
<template #on>
<keep-alive>
<router-view/>
</keep-alive>
</template>
<template #off>
<router-view/>
</template>
</Benchmark>
</template>
<script>
export default {
data () {
return {
play: false,
}
},
computed: {
page: {
get () {
return this.$route.name === 'bench-keep-alive-heavy'
},
set (value) {
if (value) {
this.$router.push({ name: 'bench-keep-alive-heavy' })
} else {
this.$router.push({ name: 'bench-keep-alive' })
}
},
},
},
watch: {
play (value) {
if (value) {
this.togglePage()
} else {
clearTimeout(this.$_timer)
}
},
},
created () {
this.count = 300
},
methods: {
togglePage () {
this.page = !this.page
if (this.play) {
this.$_timer = setTimeout(this.togglePage, 2000)
}
},
},
}
</script>
<style lang="stylus" scoped>
.keep-alive
.router-multi-view
height 100%
>>>
.simple-page,
.deferred-off
height 100%
display flex
flex-direction column
align-items center
padding 40px
box-sizing border-box
h2:not(:last-child)
margin-bottom 24px
</style>
複製程式碼
Deferred features
DeferredOff
<template>
<div class="deferred-off">
<VueIcon icon="fitness_center" class="gigantic"/>
<h2>I'm an heavy page</h2>
<Heavy v-for="n in 8" :key="n"/>
<Heavy class="super-heavy" :n="9999999"/>
</div>
</template>
複製程式碼
DeferredOn
<template>
<div class="deferred-on">
<VueIcon icon="fitness_center" class="gigantic"/>
<h2>I'm an heavy page</h2>
<template v-if="defer(2)">
<Heavy v-for="n in 8" :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>
複製程式碼
mixin Defer
export default function (count = 10) {
// @vue/component
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
},
},
}
}
複製程式碼
Vuex demo
最後一個給大家好奇去看程式碼。渲染10000條資料,一個是creash,優化後的沒壓力。