隨着互聯網的發展,越來越多的公司都在使用Vue但是隨着項目的越來越大,難免的會帶來一系列的性能問題,筆者也為了這些問題而感到頭疼,也同樣的針對Vue的性能優化進行學習,已便在項目之出把性能問題規避掉,避免沒有必要的返工。
為了方便以後能夠快速的找到相關學習內容,在這裡做一下記錄,方便以後查看,同時也想把這些內容總結一下希望能夠幫助更多的小夥伴一起學習,一起成長。Fighting~
這個時候可能會有很多小夥伴說,現在Vue3.0都快發佈了為什麼還要優化2.0的項目?因為公司80%的項目全是Vue2.0的項目,遷移的話成本太高,所以只能進行性能的優化調整。廢話就不多贅述了,直接開始吧。
活用異步組件
Vue-cli打包的時候會把所有依賴的文件打包成一個很大的一個js文件中,當用戶瀏覽網頁的時候需要把整個js文件拉取過來,這樣會導致頁面在初始化的時候,頁面會出現長時間的白屏情況,這個問題確實是蠻棘手的。
設想一下如果在頁面中有很多的功能點,每個功能點又對應着不同的功能彈窗或者表單,首先第一點頁面中的功能很多,我們不知道用戶想要使用哪個功能,需要彈出哪個彈窗或表單,如果異步組件的情況,Vue-cli在打包的時候會把異步組件單獨打包成一個文件,當用戶使用的才會去加載這個js文件內容,這樣無論是首屏的渲染起到了一定的優化的作用。
看下官網對於異步組件的說明:
在大型應用中,我們可能需要將應用分割成小一些的代碼塊,並且只在需要的時候才從服務器加載一個模塊。為了簡化,Vue允許你以一個工廠函數的方式定義你的組件,這個工廠函數會異步解析你的組件定義。Vue 只有在這個組件需要被渲染的時候才會觸發該工廠函數,且會把結果緩存起來供未來重渲染。
// 代碼截取自Vue官網
Vue.component('async-webpack-example',
() => import('./my-async-component')
)Vue官方為了解決組件加載時的等待過長,提供了異步組件加載Loading的異步組件:
// 代碼截取自Vue官網
const AsyncComponent = () => ({
// 需要加載的組件 (應該是一個 `Promise` 對象)
component: import('./MyComponent.vue'),
// 異步組件加載時使用的組件
loading: LoadingComponent,
// 加載失敗時使用的組件
error: ErrorComponent,
// 展示加載時組件的延時時間。默認值是 200 (毫秒)
delay: 200,
// 如果提供了超時時間且組件加載也超時了,
// 則使用加載失敗時使用的組件。默認值是:`Infinity`
timeout: 3000
})使用異步組件需要注意以下幾點:
- 如果只是需要異步的加載一些組件,先加載一部分,再加載一部分,那麼就可以直接使用Vue官網的那種寫法直接使用setTimeOut也不是未嘗不可的。
- 如果是點擊加載的話,一定要寫v-if,不然會報錯,說test組件未註冊。v-if是惰性的,只有當第一次值為true時才會開始初始化。
初始化減少DOM的渲染
仍然是上面所說的情況,頁面中功能點很多,但是又很多彈窗什麼的,其實對於這些彈窗最開始的時候考慮所有的彈窗只使用一個彈窗,為了節約頁面初始化的渲染,但是在實際開發過程中,雖然解決了一部分問題,彷彿在開發過程中並不是那麼樂觀,在彈窗內部出現了大量的v-if和v-show對於維護來說太難了。
也有想過使用<component/>組件,但是一個<component/>所承受的壓力可想而知不是一點半點的。但是這個問題仍然是存在的需要得到解決。沒有辦法的情況下,最後使用了兩個flag去控制彈窗的顯示與隱藏。
<template>
<div>
<el-dialog title="提示"
v-if="isRenderDialog"
:visible.sync="isShowDialog"></el-dialog>
<el-button @click="onShowDialog">Render Dialog</el-button>
</div>
</template>
<script>
export default {
data:() => ({
isRenderDialog:false,
isShowDialog:false
}),
methods: {
onShowDialog(){
!this.isRenderDialog && (this.isRenderDialog = true);
this.$nextTick(() => {
this.isShowDialog = true;
})
}
}
}
</script>上述代碼中使用兩個flag值控制Dialog一個是控制Dialog的渲染,一個控制Dialog的顯示,當用戶首次進入頁面的時候則dialog元素不會被渲染,當用戶點擊按鈕,對應的Dialog才會被渲染出來,當Dialog的DOM渲染完成使用在使用顯示Dialog。
註:在$nextTick中顯示dialog是為了保證dialog的動畫效果,如果不使用$nextTick則dialog就會很生硬的出現。
組件內部請求數據
大家在做業務的時候,可能會有這種情況,當點擊按鈕之後,需要獲取到該條數據的詳情渲染到彈窗或者側滑中,這種情況一定不在少數啦。筆者在開始做這個的時候就是,在點擊的時候直接去獲取點擊的元素的詳情數據,當數據返回之後把數據放到data中緩存,之後再傳到組件中。
這樣做不是不可行的,也是可以的,這樣就會面臨一個問題,第一點就是當彈窗中的渲染的元素過多的情況下,側滑或者彈窗的動畫效果會很卡,有的時候甚至是不動,瞬間就消失了。
最後經過反覆的實驗,把數據放到彈窗內部組件中去請求,保證彈窗或者側滑出現的時候內置元素較少,當數據沒有請求回來之前需要把彈框組件內的所有元素隱藏,使用loading代替,當彈窗或者側滑關閉的使用需要把顯示的組件銷毀掉,保證裏面的數據所佔用的內存被釋放,這樣對於整體優化還是有一些幫助的。
tamplate少計算
由於業務情況的複雜程度,難免會某一個地方添加各種條件的渲染,例如:v-if=”isHide && selectList.length && (isA || isB)”,這裡也只是舉一個簡單的例子可能在實際的開發過程中的情況遠比這個要複雜的多,這種表達式看上去雖然說是可以維護的,但是長此以往下去就會暴露問題,這樣做是很不利於維護的。


對於這種情況可以適當的使用methods或computed封裝成方法,其實這樣做的好處是方柏霓我們判斷相同的表達式,如果其他的元素也有類似的需求可以直接使用這個方法。
v-for && v-bind:key
在使用v-for循環過程中,使用:key=”item.id”這樣的代碼對於代碼的性能是很不友好的,因為當data數據更新的時候,新的狀態值會和舊的狀態值做對比,Vue在多diff算法的時候能夠更快的定位到虛擬DOM的元素上。
其實說到這裡就需要說明一下key在vue中到底起到一個什麼樣的作用,key屬性其實是vue的一個優化,上文也說了就是為了更精準高效的定位到虛擬DOM,相當於使用key給數組中某個預算綁定到了一起,如果那個key對應的數據發生了變化,直接更新對應的DOM就可以了。
對於簡短的for來說可以直接使用index作為key但是,如果大型列表的話最好還是不要使用index作為key了。舉個例子,例如數組刪除了一個元素,那麼這個元素後方元素的下標全都前移了一位,之前key對應的數據和dom就會亂了,除非重新匹配key,那就容易產生錯誤。如果重新匹配key,等於全部重新渲染一遍,違背了使用key來優化更新dom的初衷。但是如果對於Vue玩的很透的同學來說可以可以忽略這一條。
Object.freeze
如果對Vue有一定了解的小夥伴都知道Vue是通過Object.defineProperty對數據進行挾持,來最終實現視圖響應數據的變化,但是在實際的開發過程中,頁面中有一部分可能不需要進行雙向綁定,只是做單純的渲染,數據一旦綁定之後不需要再做出任何改變的時候可以使用Object.freeze對數據做解綁。
先介紹以下Object.freeze內置函數,用於對接對象,凍結後的對象不會再被修改,不能對這個對象進行添加新屬性, 不能刪除已有屬性,不能修改該對象已有屬性的可枚舉性,可配置性,可寫性.此外凍結一個對象後該對象的原型也不能進行修改。
當數據量大的時候,這能夠很明顯的減少組件初始化的時間,這裡有一個需要注意的點就是一旦被凍結的對象就再也不能被修改了。但是這裡有一個問題需要注意的是,嗒嗒嗒,敲黑板!敲黑板!敲黑板!
雖然Object.freeze在一定程度上能夠幫助我們提升一部分的數據性能,但是在使用的時候仍然需要謹慎使用。避免造成數據無法響應的問題。如果使用Object.freeze這個屬性再次給其對象屬性賦值時,則會拋出錯誤不能分配給對象的只讀屬性*。
用這種方法去提升性能如果數據量小的情況是無法感覺出來的。只有數據量大的時候,才會感覺到數據的明顯變化。
渲染前處理
在渲染數據的時候,後端所返回的數據和UI設計圖中所需要的數據格式不一致,比如:列表中需要展示一個時間,但是後端返回的是一個時間戳,那麼前端就需要對這部分數據進行處理。一般來說處理這種情況有一些辦法,使用函數,使用filter,還有就是在渲染之前把數據處理好。
筆者這裡比較建議在渲染之前把所有的數據處理好,為什麼?數據渲染之後完成之後才會去執行裏面的函數或者是過濾器,這樣會給頁面渲染造成很明顯的額外的負擔。如果對Vue3.0了解的同學可以知道,在Vue3.0中已經把filter這個功能已經去掉了,推薦使用computed來實現相同相同的效果。
猜測內容:可能尤大大也發現了filter給頁面渲染帶來的額外的負擔,並沒有對頁面的性能提升起到很大的作用。
functional
不是很多函數組件都需要方法,Vue中為了表示一個模板應該被編譯成一個功能組件,在模板中 添加了functional屬性。如果項目中的所使用的組件不是有狀態的組件,那麼就可以使用functional屬性把這個組件轉換成功能組件。
功能組件(不要與Vue的render函數混淆)是一個不包含狀態和實例的組件。功能組件是一個沒有狀態或實例的組件。由於功能組件沒有狀態,因為不需要為Vue的數據響應之類的東西做初始化動作。功能組件仍然會像出入的props一樣對數據更新做出響應,但是功能組件的自身,由於它不維護自己的狀態,同時也因此無法知道自己的數據是否已經發生了改變。在大型項目中使用功能組件以後,在對於DOM渲染有重大的改進。
由於功能組件沒有狀態,因此不需要為Vue的反應系統之類的東西進行額外的初始化。功能組件仍然會像傳入的新道具那樣對更改做出反應,但是在組件本身內,由於它不維護自己的狀態,因此無法知道何時數據已更改。
在許多情況下,功能組件可能不合適。畢竟,使用JavaScript框架的目的是構建更具反應性的應用程序。在Vue中,如果沒有適當的反應系統,則無法執行此操作。
假設我們的組件接受一個prop.user,該對象是帶有firstName和的對象lastName,並且我們想要呈現一個顯示用戶全名的模板。在功能<template>組件中,我們可以通過在組件定義上提供一個方法,然後使用$optionsVue提供的屬性來訪問我們的特殊方法來做到這一點:
<template functional>
<div>{{ $options.userFullName(props.user) }}</div>
</template>
<script>
export default {
props: {
user: Object
},
userFullName(user) {
return `${user.firstName} ${user.lastName}`
}
}
</script>子組件中處理業務
頁面中也會有很多的列表,列表中也會有各種各樣的複雜的情況,這個時候可以把一些比較繁重的業務處理存放到其子組件中。
代碼對比:
<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>優化後:
<template>
<div :style="{ opacity: number / 300 }">
<ChildComp/>
</div>
</template>
<script>
export default {
props: ['number'],
components: {
ChildComp: {
methods: {
heavy () { /* 長任務在子組件里。 */ }
},
render (h) {
return h('div', this.heavy())
}
}
}
}
</script>當組件隨着props:number的變化,組件patch重新渲染的時候,heavy長任務也會重新執行。但是如果能將沒有與父組件相互依賴的元素,拆成一個組件,在父組件需要重新渲染的時候,因為與父組件沒有依賴子組件並不會跟着重新渲染,響應的性能也能得到提升。
局部作用域
開發過程中會經常使用到一些計算屬性或者Util函數,如果我們在循環過程中,不斷的使用this.***去調用一個計算屬性的時候,每次調用這個值計算屬性都會計算一次,然而這個值卻是一個固定不變的值,就造成了很大的性能的浪費。
如果當我們使用這些屬性的時候,最好的方式是把對應的值取出來,然後再去使用。
<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>總結
以上是我通過調查資料以及個人項目中的一些小經驗得出的對於Vue性能優化的一些方案,可能文章中一些見解存在一些問題,歡迎大家在評論區指出,大家一起學習,一起進步。
原創文章,作者:投稿專員,如若轉載,請註明出處:https://www.506064.com/zh-hk/n/275214.html
微信掃一掃
支付寶掃一掃