22個Vue優化技巧(專案實用)
目錄
- 程式碼優化
- v-for 中使用 key
- v-if/v-else-if/v-else 中使用 key
- 合理的選擇 v-if 和 v-show
- 使用簡單的 計算屬性
- functional 函式式元件(2)
- 拆分元件
- 使用區域性變數
- 使用 KeepAlive
- 事件的銷燬
- 圖片載入
- 採用合理的資料處理演算法
- 其他
- 首屏/體積優化
- 體積優化
- 程式碼分割
- 網路
演示程式碼使用 Vue3 + ts + Vite 編寫,但是也會列出適用於 Vue2 的優化技巧,如果某個優化只適用於 Vue3 或者 Vue2,我會在標題中標出來。
程式碼優化
v-for 中使用 key
使用 v-for 更新已渲染的元素列表時,預設用就地複用策略;列表資料修改的時候,他會根據 key 值去判斷某個值是否修改,如果修改,則重新渲染這一項,否則複用之前的元素;
使用key的注意事項:
- 不要使用可能重複的或者可能變化 key 值(控制檯也會給出提醒)
- 不要使用陣列的 index 作為 key 值,因為如果在陣列中插入一個元素時,其後面的元素 index 將會變化。
- 如果陣列中沒有唯一的 key 值可用,可以考慮對其新增一個 key 欄位,值為 Symbol() 即可保證唯一。
v-if/v-else-if/v-else 中使用 key
可能很多人都會忽略這個點
原因:預設情況下,Vue 會盡可能高效的更新 DOM。這意味著其在相同型別的元素之間切換時,會修補已存在的元素,而不是將舊的元素移除然後在同一位置新增一個新元素。如果本不相同的元素被識別為相同,則會出現意料之外的副作用。
如果只有一個 v-if ,沒有 v-else 或者 v-if-else的話,就沒有必要加 key 了
相對於 v-for 的 key, v-if/v-else-if/v-else 中的 key 相對簡單,我們可以直接寫入固定的字串或者陣列即可
<transition> <button v-if="isEditing" v-on:click="isEditing = false" > Save </button> <button v-else v-on:click="isEditing = true" > Edit </button> </transition>
.v-enter-active,.v-leave-active { transition: all 1s; } .v-enter,.v-leave-to { opacity: 0; transform: translateY(30px); } .v-leave-active { position: absolute; }
例如對於上面的程式碼, 你會發現雖然對 button 添加了 過渡效果, 但是如果不新增 key 切換時是無法觸發過渡的
v-for 和 v-if 不要一起使用(Vue2)
此優化技巧僅限於Vue2,Vue3 中對 v-for 和 v-if 的優先順序做了調整
這個大家都知道
永遠不要把v-if和v-for同時用在同一個元素上。 引至 Vue2.x風格指南
原因是 v-for 的 優先順序高於 v-if,所以當它們使用再同一個標籤上是,每一個渲染都會先迴圈再進行條件判斷
注意: Vue3 中 v-if 優先順序高於 v-for,所以當 v-for 和 v-if 一起使用時效果類似於 Vue2 中把 v-if 上提的效果
例如下面這段程式碼在 Vue2 中是不被推薦的,Vue 也會給出對應的警告
<ul> <li v-for="user in users" v-if="user.active"> {{ user.name }} </li> </ul>
我們應該儘量將 v-if 移動到上級 或者 使用 計算屬性來處理資料
<ul v-if="active"> <li v-for="user in users"> {{ user.name }} </li> </ul>
如果你不想讓迴圈的內容多出一個無需有的上級容器,那麼你可以選擇使用 template 來作為其父元素,template 不會被瀏覽器渲染為 DOM 節點
如果我想要判斷遍歷物件裡面每一項的內容來選擇渲染的資料的話,可以使用 computed 來http://www.cppcns.com對遍歷物件進行過濾
// let usersActive = computed(()=>users.filter(user => user.active)) // template <ul> <li v-for="user in usersActive"> {{ user.name }} </li> </ul>
合理的選擇 v-if 和 v-show
v-if 和 v-show 的區別相比大家都非常熟悉了; v-if 通過直接操作 DOM 的刪除和新增來控制元素的顯示和隱藏;v-show 是通過控制 DOM 的 display 熟悉來控制元素的顯示和隱藏
由於對 DOM 的 新增/刪除 操作效能遠遠低於操作 DOM 的 CSS 屬性
所以當元素需要頻繁的 顯示/隱藏 變化時,我們使用 v-show 來提高效能。
當元素不需要頻繁的 顯示/隱藏 變化時,我們通過 v-if 來移除 DOM 可以節約掉瀏覽器渲染這個的一部分DOM需要的資源
使用簡單的 計算屬性
應該把複雜計算屬性分割為儘可能多的更簡單的 property。
易於測試
當每個計算屬性都包含一個非常簡單且很少依賴的表示式時,撰寫測試以確保其正確工作就會更加容易。
易於閱讀
簡化計算屬性要求你為每一個值都起一個描述性的名稱,即便它不可複用。這使得其他開發者 (以及未來的你) 更容易專注在他們關心的程式碼上並搞清楚發生了什麼。
更好的“擁抱變化”
任何能夠命名的值都可能用在檢視上。舉個例子,我們可能打算展示一個資訊,告訴使用者他們存了多少錢;也可能打算計算稅費,但是可能會分開展現,而不是作為總價的一部分。
小的、專注的計算屬性減少了資訊使用時的假設性限制,所以需求變更時也用不著那麼多重構了。
引至 Vue2風格指南
computed 大家後很熟悉, 它會在其表示式中依賴的響應式資料傳送變化時重新計算。如果我們在一個計算屬性中www.cppcns.com書寫了比較複雜的表示式,那麼其依賴的響應式資料也任意變得更多。當其中任何一個依賴項變化時整個表示式都需要重新計算
let price = computed(()=>{ let basePrice = manufactureCost / (1 - profitMargin) return ( basePrice - basePrice * (discountPercent || 0) ) })
當 manufactureCost、profitMargin、discountPercent 中任何一個變化時都會重新計算整個 price。
但是如果我們改成下面這樣
let basePrice = computed(() => manufactureCost / (1 - profitMargin)) let discount = computed(() => basePrice * (discountPercent || 0)) let finalPrice = computed(() => basePrice - discount)
如果當 discountPercent 變化時,只會 重新計算 discount 和 finalPrice,由於 computed 的快取特性,不會重新計算 basePrice
functional 函式式元件(Vue2)
注意,這僅僅在 Vue2 中被作為一種優化手段,在 3.x 中,有狀態元件和函式式元件之間的效能差異已經大大減少,並且在大多數用例中是微不足道的。因此,在 SFCs 上使用functional的開發人員的遷移路徑是刪除該 attribute,並將props的所有引用重新命名為$props,將attrs重新命名為$attrs。
優化前
<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>
優化後
<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>
- 沒有this(沒有例項)
- 沒有響應式資料
拆分元件
什麼?你寫的一個vue檔案有一千多行程式碼?🤔
合理的拆分元件不僅僅可以優化效能,還能夠讓程式碼更清晰可讀。單一功能原則嘛
源自 https://slides.com/akryum/vueconfus-2019#/4/0/3
優化前
<template> <div :style="{ opacity: number / 300 }"> <div>{{ heavy() }}</div> </div> </template> <script> export default { props: ['number'],methods: { heavy () { /* HEAVY TASK */ } } } </script>
優化後
<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>
由於 Vue 的更新是元件粒度的,雖然每一幀都通過資料修改導致了父元件的重新渲染,但是ChildComp卻不會重新渲染,因為它的內部也沒有任何響應式資料的變化。所以優化後的元件不會在每次渲染都執行耗時任務
使用區域性變數
優化前
<template> <div :style="{ opacity: start / 300 }">{{ result }}</div> </template> <script> imporhttp://www.cppcns.comt { 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>
優化後
<template> <div :style="{ opacity: start / 300 }"> {{ result }}</div> </template> <script> import { heavy } from '@/utils' export default { props: ['start'],result () { const base = this.base let result = this.start for (let i = 0; i < 1000; i++) { result += heavy(base) } return result } } } </script>
這裡主要是優化前後的元件的計算屬性 result 的實現差異,優化前的元件多次在計算過程中訪問 this.base,而優化後的元件會在計算前先用區域性變數 base,快取 this.base,後面直接訪問 base。
那麼為啥這個差異會造成效能上的差異呢,原因是你每次訪問 this.base 的時候,由於 this.base 是一個響應式物件,所以會觸發它的 getter,進而會執行依賴收集相關邏輯程式碼。類似的邏輯執行多了,像示例這樣,幾百次迴圈更新幾百個元件,每個元件觸發 computed 重新計算,然後又多次執行依賴收集相關邏輯,效能自然就下降了。
從需求上來說,this.base 執行一次依賴收集就夠了,把它的 getter 求值結果返回給區域性變數 base,後續再次訪問 base 的時候就不會觸發 getter,也不會走依賴收集的邏輯了,效能自然就得到了提升。
引至 揭祕 Vue.js 九個效能優化技巧
使用 KeepAlive
在一些渲染成本比較高的元件需要被經常切換時,可以使用 keep-alive 來快取這個元件
而在使用 keep-alive 後,被 keep-alive 包裹的元件在經過第一次渲染後,的 vnode 以及 DOM 都會被快取起來,然後再下一次再次渲染該元件的時候,直接從快取中拿到對應的 vnode 和 DOM,然後渲染,並不需要再走一次元件初始化,render 和 patch 等一系列流程,減少了 script 的執行時間,效能更好。
注意: 濫用 keep-alive 只會讓你的應用變得更加卡頓,因為他會長期佔用較大的記憶體
事件的銷燬
當一個元件被銷燬時,我們應該清除元件中新增的 全域性事件 和 定時器 等來防止記憶體洩漏
Vue3 的 HOOK 可以讓我們將事件的宣告和銷燬寫在一起,更加可讀
function scrollFun(){ /* ... */} document.addEventListener("scroll",scrollFun) onBeforeUnmount(()=>{ document.removeEventListener("scroll",scrollFun) })
Vue2 依然可以通過 $once 來做到這樣的效果,當然你也可以在 optionsAPI beforeDestroy 中銷燬事件,但是我更加推薦前者的寫法,因為後者會讓相同功能的程式碼更分散
function scrollFun(){ /* ... */} document.addEventListener("scroll",scrollFun) this.$once('hook:beforeDestroy',()=>{ document.removeEventListener("scroll",scrollFun) })
function scrollFun(){ /* ... */} export default { created() { document.addEventListener("scroll",scrollFun) },beforeDestroy(){ document.removeEventListener("scroll",scrollFun) } }
圖片載入
圖片懶載入:適用於頁面上有較多圖片且並不是所有圖片都在一屏中展示的情況,vue-lazyload 外掛給我們提供了一個很方便的圖片懶載入指令 v-lazy
但是並不是所有圖片都適合使用懶載入,例如 banner、相簿等 更加推薦使用圖片預載入技術,將當前展示圖片的前一張和後一張優先下載。
採用合理的資料處理演算法
這個相對比較考驗 資料結構和演算法 的功底
例如一個將陣列轉化為多級結構的方法
/**
* 陣列轉樹形結構,時間複雜度O(n)
* @param list 陣列
* @param idKey 元素id鍵
* @param parIdKey 元素父id鍵
* @param parId 第一級根節點的父id值
* @return {[]}
*/
function listToTree (list,idKey,parIdKey,parId) {
let map = {};
let result = [];
let len = list.length;
// 構建map
for (let i = 0; i < len; i++) {
//將陣列中資料轉為鍵值對結構 (這裡的陣列和obj會相互引用,這是演算法實現的重點)
map[list[i][idKeyJjNFArHq]] = list[i];
}
// 構建樹形陣列
for(let i=0; i < len; i++) {
let itemParId = list[i][parIdKey];
// 頂級節點
if(itemParId === parId) {
result.push(list[i]);
continue;
}
// 孤兒節點,捨棄(不存在其父節點)
if(!map[itemParId]){
continue;
}
// 將當前節點插入到父節點的children中(由於是引用資料型別,obj中對於節點變化,result中對應節點會跟著變化)
if(map[itemParId].children) {
map[itemParId].children.push(list[i]);
} else {
map[itemParId].children = [list[i]];
}
}
return result;
}
其他
除了上面說的方法以外還有很多優化技巧,只是我在專案並不是太常用🤣
- 凍結物件(避免不需要響應式的資料變成響應式)
- 長列表渲染-分批渲染
- 長列表渲染-動態渲染(vue-virtual-scroller)
- ...
首屏/體積優化
我在專案中關於首屏優化主要有以下幾個優化方向
- 體積
- 程式碼分割
- 網路
體積優化
- 壓縮打包程式碼: webpack 和 vite 的生產環境打包預設就會壓縮你的程式碼,這個一般不需要特殊處理,webpack 也可以通過對應的壓縮外掛手動實現
- 取消 source-map: 可以檢視你的打包產物中是否有 .map 檔案,如果有你可以將 source-map 的值設定為false或者空來關閉程式碼對映(這個佔用的體積是真的大)
- 打包啟用 gizp 壓縮: 這個需要伺服器也開啟允許 gizp 傳輸,不然啟用了也沒啥用( webpack 有對應的 gzip 壓縮外掛,不太版本的 webpack 壓縮外掛可能不同,建議先到官網查詢)
程式碼分割
程式碼分割的作用的將打包產物分割為一個一個的小產物,其依賴 esModule。所以當你使用 import() 函式來匯入一個檔案或者依賴,那麼這個檔案或者依賴就會被單獨打包為一個小產物。 路由懶載入 和 非同步元件 都是使用這個原理。
- 路由懶載入
- 非同步元件
對於 UI庫 我一般不會使用按需載入元件,而是比較喜歡 CDN 引入的方式來優化。
網路
- CDN: 首先就是上面的說的 CDN 引入把,開發階段使用本地庫,通過配置 外部擴充套件(Externals) 打包時來排除這些依賴。然後在 html 檔案中通過 CDN 的方式來引入它們
- Server Push: HTTP2已經相對成熟了;經過上面的 CDN 引入,我們可以對使用 HTTP2 的 Server Push 功能來讓瀏覽器提前載入 這些 CDN 和 其他檔案。
- 開啟 gzip: 這個上面已經說過了,其原理就是當客戶端和服務端都支援 gzip 傳輸時,服務端會優先發送經過 gzip 壓縮過的檔案,然後客戶端接收到在進行解壓。
- 開啟快取: 一般我使用的是協商快取,但是這並不適用於所有情況,例如對於使用了 Server Push 的檔案,就不能隨意的修改其檔名。所以我一般還會將生產的主要檔案固定檔名
到此這篇關於22個Vue優化技巧(專案實用)的文章就介紹到這了,更多相關Vue優化技巧內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!