簡介
vue
函數式組件大部分人在開發過程中用到的不多,就連官方文檔位置放置的也比較隱晦,但是在我們對項目做性能優化時,卻是一個不錯的選擇。本文將對函數式組件初始化過程做一個系統性的闡述,通過本文,你將了解到以下內容:
- 什么是函數式組件
- 函數式組件與普通組件間的差異
vue
相似性能優化點
什么是函數式組件
函數式組件即無狀態組件,沒有data
、computed
、watch
,也沒有生命周期方法,組件中也沒有this
上下文,只有props
傳參。在開發中,有很多組件僅僅只用到了props
和插槽,這部分組件就可以提煉為函數式組件。借用官網demo
,最簡單的函數式組件如下:
Vue.component('my-component', {
functional: true,
// Props 是可選的
props: {
// ...
},
// 為了彌補缺少的實例
// 提供第二個參數作為上下文
render: function (createElement, context) {
// ...
}
})
函數式組件與普通組件間的差異
組件實例化過程大致分為四步,狀態初始化 --> 模板編譯 --> 生成VNode
--> 轉換為真實DOM
。接下來對比普通組件與函數式組件常用配置項,比較下差異。
功能點名稱 | 普通組件 | 函數式組件 | 描述 |
---|---|---|---|
vm | Y | N | 組件作用域 |
hooks | Y | N | 生命周期鈎子 |
data | Y | N | 數據對象聲明 |
computed | Y | N | 計算屬性 |
watch | Y | N | 偵聽器 |
props | Y | Y | 屬性 |
children | Y | Y | VNode 子節點的數組 |
slots | Y | Y | 一個函數,返回了包含所有插槽的對象 |
scopedSlots | Y | Y | 作用域插槽的對象 |
injections | Y | Y | 依賴注入 |
listeners | Y | Y | 事件監聽 |
parent | Y | Y | 對父組件的引用 |
從上表中可以看出,普通組件與函數式組件最大的差別在於函數式組件沒有獨立作用域,沒有響應式數據聲明。沒有獨立作用域,會有以下優點:
-
沒有組件實例化(
new vnode.componentOptions.Ctor(options)
),函數式組件獲取VNode
僅僅是普通函數調用- 無公共屬性、方法拷貝
- 無生命周期鈎子調用
-
函數式組件直接掛載到父組件中,縮短首次渲染、
diff
更新路徑- 函數式組件在父組件生成
VNode
時,函數式組件render
方法會被調用,生成VNode
掛載到父組件children
中,patch
階段可直接轉換成真是DOM
,普通組件則在createElm
時,走組件初始化流程。 diff
更新時,函數式組件調用render
,直接創建普通VNode
,而普通組件創建的VNode
的是包含組件作用域的,diff
操作時,還有額外調用updateChildComponent
更新屬性、自定義事件等,調用鏈路會比較長。
- 函數式組件在父組件生成
vue性能優化點
函數式組件帶來的性能提升主要體現在縮短渲染路徑與減少組件嵌套層級,前者與瀏覽器重繪回流有異曲同工之處,后者可以降低時間復雜度。
無論何種性能優化,能從代碼層面做優化的,無疑是代價最小的,雖然有時效果不是很明顯,但是積少成多。在vue
中,有不少與上述相似的點,可以提升代碼執行效率。
合理聲明data
中數據,確保data
中聲明數據都是必須的
很多時候有一些數據沒必要聲明在data
中,比如需要組件內共享,但不需要響應式處理的數據。data
中的數據,對象都會對其深度優先用Object.defineProperty
聲明,數組也會攔截基本操作方法。不必要的聲明會造成無意義的數據劫持。
合理使用computed
與watch
computed
與watch
最大的區別在於computed
是惰性加載的。惰性加載主要體現在兩個方面:
- 依賴狀態發生改變時,不會立即觸發,只是改變當前
Watcher
實例的dirty
屬性值為true
- 當對計算屬性值取操作時,當且僅當
watcher.dirty === true
時,才會觸發計算
以上兩點特性,能夠避免一些不必要的代碼執行,具體代碼如下所示:
// src\core\instance\state.js
function createComputedGetter (key) {
return function computedGetter () {
// 獲取實例上的computed屬性的watcher實例
const watcher = this._computedWatchers && this._computedWatchers[key]
if (watcher) {
// 當且僅當computed依賴屬性發生變化 && 對計算屬性進行取操作,才會調用Watcher的update方法,將dirty置為true
if (watcher.dirty) {
// 調用get方法,獲取到computed的值
watcher.evaluate()
}
if (Dep.target) {
watcher.depend()
}
return watcher.value
}
}
}
// src\core\observer\watcher.js
update () {
// computed watcher lazy === true
if (this.lazy) {
// 標記懶執行是否可執行狀態,false不執行計算屬性計算
this.dirty = true
} else if (this.sync) { // 同步執行
this.run()
} else {
// 將當前watcher放入到watcher隊列
queueWatcher(this)
}
}
v-for綁定key值
v-for
循環定義key
值目的是便於精准找到diff
比對節點,避免一些無意義的比對。
普通diff: 從頭尾開始,新舊節點頭尾分別比較,游標向中間靠攏,當且僅當一個節點遍歷結束后,diff
流程結束
帶有key值diff: 根據key
值維護一個hash
表,每次循環精准定位到更新目標節點,當且僅當一個節點遍歷結束后,diff
流程結束
思考
在vue
中,很多性能優化點都是縮短代碼執行路徑,尤其在存在大量計算邏輯中,性能的提升會有肉眼可見的效果。實際開發中,也有不少場景可以用到此類優化方法,舉個最簡單的例子,關鍵詞高亮匹配。實現這個操作,需要以下幾步:
- 獲取匹配關鍵詞,將關鍵詞進行格式化(對正則表達式中有意義的字符串進行轉義)
- 動態生成匹配的正則表達式
- 根據正則表達式進行
replace
操作
有些時候,第一, 二步我們可以省略,直接執行第三步即可,因為輸入關鍵字可能存在相同的,因此我們可以將字符串與正則表達式緩存在Map
中,下次匹配時,如果存在緩存,直接從緩存中拿即可。
vue
模板編譯用到的就是這個特性,每次會把編譯的模板字符串作為key
值,render
方法作為value
,緩存起來,如果遇到一樣的模板,可以省去編譯流程,帶來一定的性能提升。
小結
養成良好的編碼習慣,對於個人能力,也是一個不錯的提升。