Vue2存在源碼可維護性差、性能問題和API相容性不足等缺點。Vue3通過monorepo管理、TypeScript開發、性能優化和引入Composition API等方式,顯著提升了源碼可維護性、編程體驗、TypeScript支持和邏輯復用實踐,從源碼、性能和語法API三方面進行了優化。 ...
不足與展望
Vue2的不足
- 源碼自身的可維護性較差;
- 數據量大後帶來渲染和更新的性能問題;
- 存在一些為了相容但是和雞肋的API。
對Vue3的期望
- 更好的編程體驗;
- 更好的
TypeScript
支持; - 更好的邏輯復用實踐。
從源碼、性能、語法API三大方面優化框架。
Vue3的優化
源碼優化
源碼優化的目的是讓代碼更易於開發和維護。
主要體現在使用monorepo
管理源碼倉庫,以及使用TypeScript
進行開發。
monorepo
monorepo
將模塊拆分到不同的package
中,每個package
擁有各自的API
、類型定義和測試。
這樣使得模塊拆分更細化,職責劃分更明確,模塊之間的依賴關係也更加明確。
開發人員也更容易閱讀、理解和更改所有模塊源碼,提高了代碼的可維護性。
使用mororepo
還有一個好處是:package
可以獨立於Vue.js
使用,例如reactivity
響應式庫。
如果用戶僅需要使用Vue.js 3.0
的響應式功能,可以單獨依賴這個package
而不需要依賴整個Vue.js
,減小了引用包的體積大小。
TypeScript
使用TypeScript
的好處是:
- 可以在編碼階段做類型檢查;
- 定義介面類型,有利於
IDE
對變數類型的推導,開發更輕鬆。
Vue
曾經用過flow
作為類型工具,但後來轉TypeScript
了,原因在於:
TypeScript
的社區更活躍;TypeScript
在IDE
中的支持更好;TypeScript
有明確的版本發佈策略,使得維護和升級更加可控,Flow
的團隊後期爛尾。
一篇相關的文章:Flow vs Typescript: From Flow to Typescript. Why? - DEV Community
性能優化
減小包體積
- 移除一些冷門的
feature
,比如inline-template
; - 引入
tree-shaking
技術。
tree-shaking
tree-shaking
依賴ES2015
的模塊語法,通過編譯階段的靜態分析,找到沒有引入的模塊並打上標記。
在項目中沒有引入的模塊或組件,它們對應的代碼就不會被打包,也就間接減小了項目引入Vue.js
包體積的目的。
數據劫持優化
Vue
最獨特的特性之一,是其非侵入性的響應式系統。
實現非侵入式的響應式系統的關鍵在於攔截對數據的讀寫操作。
響應式數據需要配置getter
和setter
。當渲染函數執行時,讀取了數據,觸發getter
,在getter
中將object[key]
到依賴的映射記錄到Watcher
中。
依賴是一個函數,對於在
template
中使用的響應式數據來說,它的依賴就是渲染函數。
當響應式數據被更新時,即被賦值,會觸發setter
,在setter
中根據object
和key
去Watcher
中找到相對應的依賴函數,執行。即觸發了視圖更新。
總結:
- 在
getter
中使用track
函數收集依賴; - 在
setter
中使用trigger
函數觸發更新。
Vue2的做法
在Vue2
中使用Object.defineProperty
這個方法攔截對對象指定屬性的讀寫操作。
Object.defineProperty
為對象的指定屬性設置getter
和setter
。
對於一個嵌套層級較深的對象來說,為了遍歷對象的每個屬性,需要遞歸調用,為每個屬性都設置getter&setter
。
缺點:
-
遞歸調用會導致性能較差;
-
無法檢測
property
的添加或移除,後來新增的屬性無法實現響應式;需要使用全局的
Vue.set
方法或者組件內部局部的this.$set
方法,讓Vue
有能力將依賴添加到Watcher
中進行管理。 -
使用索引設置數組的某一項的值,無法被監測到;
-
修改數組的
length
屬性,不會觸發更新; -
無法應對
Map
、Set
這些集合類型的響應式,需要開發者自己想辦法解決。
這些缺點都來自於Object.defineProperty
這個API本身的局限性。
Vue3的做法
在Vue3
中,數據攔截使用了ES6
的Proxy API
,這個API
可以直接將目標對象作為整體,攔截對它的各種操作,包括:
- 對某個屬性的讀/寫;
- 新增屬性;
- 刪除屬性;
Proxy
可以Map/Set
在getter
攔截到對get/set/add
等方法的調用。
因此,Vue3
相比於Vue2
:
- 解決了遞歸配置
getter&setter
的問題; - 解決了新增/刪除屬性沒有響應式的問題;
- 實現了響應式的
Map/Set
。
可以說Vue3
在響應式這一部分的提升來自於ES6
提供了更現代化更高效的API
。
編譯優化
在Vue2
中,從new Vue()
到生成DOM
的過程大致如下:
上述的響應式過程發生在init
階段,而template
編譯為render function
的階段可以在打包過程中完成。
而在運行時中,耗時較多的是patch
階段。
Vue3
在編譯階段優化了編譯的結果,實現了對patch
過程的優化。
具體來說是通過flag
標記、靜態提升的手段來實現的。
在Vue2
中,數據更新並觸發重新渲染的粒度是組件級的。
在組件內部仍然需要遍歷整顆VNode樹,如果組件包含許多靜態內容,那麼diff
演算法會在靜態內容上浪費許多時間。
也就是說diff
演算法的效率由組件的虛擬DOM樹規模決定,而不是由動態節點的規模決定。
Vue3
使用了Block Tree
來優化diff
過程。
Block Tree
Block Tree 是一個將模板基於動態節點指令切割的嵌套區塊,每個區塊內部的節點結構是固定的(
STABLE_FLAGMENT
)。對於結構固定的區塊,僅需要使用一個
Array
來追蹤其包含的動態節點。
藉助 Block Tree,Vue3
將 vnode
的更新性能調整為與動態內容的數量相關。
其它
Vue3
在編譯階段實現了對Slot
的編譯優化、事件偵聽函數的緩存優化;Vue3
重寫了diff
演算法。
語法API優化
Vue3
在語法上的一個大的變動主要是提供了Composition API
,即組合式API。
在Vue2
中,編寫組件本質是在編寫一個“包含了描述組件選項的對象”,被稱為Options API
,即選項式API。
選項式API適合開發小型組件,選項內容一目瞭然。對於包含多個邏輯關註點的大型組件來說,使用選項式API會導致一個邏輯關註點的代碼被分散到不同選項中。
組合式API的好處在於可以將邏輯關註點相關的代碼封裝到一個函數里。
邏輯復用優化
在Vue2
中,存在mixins
這種邏輯復用的方式。它的缺點在於當一個組件混入多個來源的mixins
後,會出現命名衝突和數據來源不清晰的問題。
在Vue3
中,通過組合式函數解決了這一問題,組合式函數將可復用邏輯封裝成函數(按照規範通常以use
作為函數名開頭),通常是返回一個對象。這是一種更靈活且更穩定的做法,因為:
- 對於組合式函數來說,可以選擇性地向外暴露API;
- 對於使用地組件來說,解構返回的對象時,可以重命名避免命名衝突。
組合式API的其它好處:
- 更好的類型支持:組合式API都是函數,類型更容易推導,不想選項式API所有的內容都要通過
this
聯繫; - 組合式API對
tree-shaking
比較友好,代碼也更容易壓縮。