https://v3.cn.vuejs.org/guide/migration/introduction.html#%E6%A6%82%E8%A7%88
首先熟空,為什么要優(yōu)化?
?????Vue3 組合式 API(Composition API) 主要用于在大型組件中提高代碼邏輯的可復(fù)用性。
?????vue2.x option Api的傳統(tǒng)組件隨著業(yè)務(wù)復(fù)雜度越來越高锅棕,代碼量會不斷的加大,整個代碼邏輯都不易閱讀和理解衣式。
?????Vue3 使用組合式 API 的地方為 setup俯艰。
?????在 setup 中,我們可以按邏輯關(guān)注點對部分代碼進行分組缰揪,然后提取邏輯片段并與其他組件共享代碼。因此葱淳,組合式 API(Composition API) 允許我們編寫更有條理的代碼钝腺。
另,
compositon api
提供了以下幾個函數(shù):
setup
ref
reactive
watchEffect
watch
computed
toRefs
- 生命周期的
hooks
優(yōu)化
一赞厕、 源碼的優(yōu)化
1. 更好的代碼管理方式:monorepo
vue2.x | vue3.x |
---|---|
Vue.js 2.x 的源碼托管在 src 目錄艳狐,然后分別依據(jù)功能拆分出了: 1. compiler(模板編譯的相關(guān)代碼) 2. core(與平臺無關(guān)的通用運行時代碼) 3. platforms(平臺專有代碼) 4. server(服務(wù)端渲染的相關(guān)代碼) 5. sfc(.vue 單文件解析相關(guān)代碼) 6. shared(共享工具代碼) 等目錄: |
Vue.js 3.0 ,整個源碼是通過 monorepo 的方式維護 根據(jù)功能將不同的模塊拆分到packages 目錄下面不同的子目錄中 |
vue2.x代碼結(jié)構(gòu)
|
vue3.x代碼結(jié)構(gòu)
|
優(yōu)點:
- 相對于 Vue.js 2.x 的源碼組織方式皿桑,monorepo 把這些模塊拆分到不同的 package 中毫目,每個 package有各自的API蔬啡、類型定義和測試。這樣使得模塊拆分更細化镀虐,職責劃分更明確箱蟆,模塊之間的依賴關(guān)系也更加明確,開發(fā)人員也更容易閱讀刮便、理解和更改所有模塊源碼空猜,提高代碼的可維護性。
- 另外一些 package(比如 reactivity 響應(yīng)式庫)是可以獨立于 Vue.js 使用的恨旱,這樣用戶如果只想使用 Vue.js 3.0 的響應(yīng)式能力辈毯,可以單獨依賴這個響應(yīng)式庫而不用去依賴整個 Vue.js,減小了引用包的體積大小搜贤,而 Vue.js 2 .x 是做不到這一點的谆沃。
2. 有類型的 JavaScript:TypeScript
vue2.x | vue3.x |
---|---|
Flow Flow 是 Facebook 出品的 JavaScript 靜態(tài)類型檢查工具 優(yōu)點:它可以以非常小的成本對已有的 JavaScript 代碼遷入,非常靈活 缺點:但是 Flow 對于一些復(fù)雜場景類型的檢查仪芒,支持得并不好 |
TypeScript TypeScript提供了更好的類型檢查管毙,能支持復(fù)雜的類型推導(dǎo) 由于源碼就使用 TypeScript 編寫,也省去了單獨維護 d.ts 文件的麻煩 |
二桌硫、 性能優(yōu)化
1. 源碼體積優(yōu)化
- 移除一些冷門的
feature
(比如 filter夭咬、inline-template 等); - 引入
tree-shaking
的技術(shù)铆隘,減少打包體積卓舵。
tree-shaking原理
tree-shaking 依賴 ES2015 模塊語法的靜態(tài)結(jié)構(gòu)(即 import 和 export),通過編譯階段的靜態(tài)分析膀钠,找到?jīng)]有引入的模塊并打上標記掏湾。
舉個栗子,一個 math 模塊定義了 2 個方法 square(x) 和 cube(x) :
export function square(x) {
return x * x
}
export function cube(x) {
return x * x * x
}
我們在另一個模塊外面只引入了 cube 方法:
import { cube } from './math.js'
// do something with cube
最終 math 模塊會被 webpack 打包生成如下代碼:
/* 1 */
/***/
(function(module, __webpack_exports__, __webpack_require__) {
'use strict';
/* unused harmony export square */
/* harmony export (immutable) */ __webpack_exports__['a'] = cube;
function square(x) {
return x * x;
}
function cube(x) {
return x * x * x;
}
});
由上可得肿嘲,未被引入的 square 模塊被標記了融击, 然后壓縮階段會利用例如 uglify-js、terser 等壓縮工具真正地刪除這些沒有用到的代碼雳窟。
也就是說尊浪,利用 tree-shaking 技術(shù),任何一個函數(shù)封救,僅僅在用到的時候才打包拇涤,沒用到的模塊都被搖掉,打包的整體體積變小誉结。
2. 響應(yīng)式系統(tǒng)——數(shù)據(jù)劫持優(yōu)化
vue2.x中采用
Object.defineProperty
來劫持整個對象鹅士,然后進行深度遍歷所有屬性,給每個屬性添加getter和setter惩坑,實現(xiàn)響應(yīng)式:
- 缺點:它必須預(yù)先知道要攔截的 key 是什么掉盅,所以它并不能檢測對象屬性的添加和刪除也拜,所以提供了
vue.set
和vue.delete
方式進行處理,但是對于開發(fā)者來說顯然是不那么友好的趾痘。
vue3.x采用proxy
重寫了響應(yīng)式系統(tǒng)慢哈,因為proxy可以對整個對象進行監(jiān)聽,所以不需要深度遍歷:
- 可以監(jiān)聽動態(tài)屬性的添加
- 可以監(jiān)聽到數(shù)組的索引和數(shù)組length屬性
- 可以監(jiān)聽刪除屬性
3. 編譯階段的優(yōu)化
- diff算法優(yōu)化
- 靜態(tài)提升
- 事件監(jiān)聽緩存
- SSR優(yōu)化
3.1 diff算法優(yōu)化
雖然 Vue 能保證觸發(fā)更新的組件最小化扼脐,但在單個組件內(nèi)部依然需要遍歷該組件的整個 vnode 樹,舉個例子奋刽,比如我們要更新這個組件:
<div>
<p>老八食堂</p>
<p>{{ message }}</p>
</div>
則整個 diff 過程如圖所示(在 Vue 2.x 的全量對比
模式下):
可以看到瓦侮,這段代碼中只有一個動態(tài)節(jié)點,所以這里有很多 diff 和遍歷其實都是不需要的佣谐,這就會導(dǎo)致 vnode 的性能跟模版大小正相關(guān)肚吏,跟動態(tài)節(jié)點的數(shù)量無關(guān),當一些組件的整個模版內(nèi)只有少量動態(tài)節(jié)點時狭魂,這些遍歷都是性能的浪費罚攀。
而對于上述例子,理想狀態(tài)只需要 diff 這個綁定 message 動態(tài)節(jié)點的 p 標簽即可雌澄。
在 Vue 3.0 中斋泄,對 diff 算法進行了優(yōu)化,在創(chuàng)建虛擬 DOM 時镐牺,根據(jù) DOM 內(nèi)容是否會發(fā)生變化炫掐,而給予相對應(yīng)類型的靜態(tài)標記(PatchFlag)
,如下圖所示:
觀察上圖睬涧,不難發(fā)現(xiàn)試圖的更新只對帶有 flag 標記的標簽進行了對比(diff)募胃,所以只進行了 1 次比較,而相同情況下畦浓,Vue 2.x 則進行了 3 次比較痹束。這便是 Vue 3.0 比 Vue2.x 性能好的第一個原因。
- 關(guān)于靜態(tài)類型枚舉如下:
export const enum PatchFlags {
TEXT = 1,// 動態(tài)的文本節(jié)點
CLASS = 1 << 1, // 2 動態(tài)的 class
STYLE = 1 << 2, // 4 動態(tài)的 style
PROPS = 1 << 3, // 8 動態(tài)屬性讶请,不包括類名和樣式
FULL_PROPS = 1 << 4, // 16 動態(tài) key祷嘶,當 key 變化時需要完整的 diff 算法做比較
HYDRATE_EVENTS = 1 << 5, // 32 表示帶有事件監(jiān)聽器的節(jié)點
STABLE_FRAGMENT = 1 << 6, // 64 一個不會改變子節(jié)點順序的 Fragment
KEYED_FRAGMENT = 1 << 7, // 128 帶有 key 屬性的 Fragment
UNKEYED_FRAGMENT = 1 << 8, // 256 子節(jié)點沒有 key 的 Fragment
NEED_PATCH = 1 << 9, // 512
DYNAMIC_SLOTS = 1 << 10, // 動態(tài) solt
HOISTED = -1, // 特殊標志是負整數(shù)表示永遠不會用作 diff
BAIL = -2 // 一個特殊的標志,指代差異算法
}
3.2 靜態(tài)提升
Vue3中對不參與更新的元素夺溢,會做靜態(tài)提升抹蚀,只會被創(chuàng)建一次,在渲染時直接復(fù)用企垦。
這樣就免去了重復(fù)的創(chuàng)建節(jié)點环壤,大型應(yīng)用會受益于這個改動,免去了重復(fù)的創(chuàng)建操作钞诡,優(yōu)化了運行時候的內(nèi)存占用郑现。
<span>你好</span>
<div>{{ message }}</div>
沒有做靜態(tài)提升之前:
export function render(_ctx, _cache, $props, $setup, $data, $options) {
return (_openBlock(), _createBlock(_Fragment, null, [
_createVNode("span", null, "你好"),
_createVNode("div", null, _toDisplayString(_ctx.message), 1 /* TEXT */)
], 64 /* STABLE_FRAGMENT */))
}
做了靜態(tài)提升之后:
const _hoisted_1 = /*#__PURE__*/_createVNode("span", null, "你好", -1 /* HOISTED */)
export function render(_ctx, _cache, $props, $setup, $data, $options) {
return (_openBlock(), _createBlock(_Fragment, null, [
_hoisted_1,
_createVNode("div", null, _toDisplayString(_ctx.message), 1 /* TEXT */)
], 64 /* STABLE_FRAGMENT */))
}
// Check the console for the AST
靜態(tài)內(nèi)容_hoisted_1被放置在render 函數(shù)外湃崩,每次渲染的時候只要取 _hoisted_1 即可。
同時 _hoisted_1 被打上了 PatchFlag 接箫,靜態(tài)標記值為 -1 攒读,特殊標志是負整數(shù)表示永遠不會用于 Diff。
3.3 事件監(jiān)聽緩存(cacheHandler)
默認情況下 @click 事件
被認為是動態(tài)變量辛友,所以每次更新視圖的時候都會追蹤它的變化薄扁。但是正常情況下,我們的 @click 事件
在視圖渲染前和渲染后废累,都是同一個事件邓梅,基本上不需要去追蹤它的變化,所以 Vue 3.0 對此作出了相應(yīng)的優(yōu)化叫事件監(jiān)聽緩存邑滨,舉個栗子:
<div>
<p @click="handleClick">屋里一giao</p>
</div>
編譯后如下圖所示(還未開啟 cacheHandler):
在未開啟事件監(jiān)聽緩存的情況下日缨,我們看到這串代碼編譯后被靜態(tài)標記為 8,之前講解過被靜態(tài)標記的標簽就會被拉去做比較掖看,而靜態(tài)標記 8 對應(yīng)的是“動態(tài)屬性匣距,不包括類名和樣式”。
@click
被認為是動態(tài)屬性哎壳,所以我們需要開啟 Options
下的 cacheHandler
屬性毅待,如下圖所示:開啟
cacheHandler
之后,編譯后的代碼已經(jīng)沒有靜態(tài)標記(PatchFlag)
归榕,也就表明圖中 P 標簽不再被追蹤比較變化恩静,也就是說下次diff算法的時候直接使用,進而提升了 Vue 的性能蹲坷。3.4 SSR優(yōu)化
當靜態(tài)內(nèi)容大到一定量級時候驶乾,會用createStaticVNode
方法在客戶端去生成一個static node
,這些靜態(tài)node循签,會被直接innerHtml
级乐,就不需要創(chuàng)建虛擬DOM對象,然后根據(jù)對象渲染
<div>
<div>
<span>你好</span>
</div>
... // 很多個靜態(tài)屬性
<div>
<span>{{ message }}</span>
</div>
</div>
編譯后
import { mergeProps as _mergeProps } from "vue"
import { ssrRenderAttrs as _ssrRenderAttrs, ssrInterpolate as _ssrInterpolate } from "@vue/server-renderer"
export function ssrRender(_ctx, _push, _parent, _attrs, $props, $setup, $data, $options) {
const _cssVars = { style: { color: _ctx.color }}
_push(`<div${
_ssrRenderAttrs(_mergeProps(_attrs, _cssVars))
}><div><span>你好</span>...<div><span>你好</span><div><span>${
_ssrInterpolate(_ctx.message)
}</span></div></div>`)
}
參考文獻:
https://juejin.cn/post/6903171037211557895
https://vue3js.cn/interview/vue3/performance.html
https://juejin.cn/post/6850418112878575629#heading-5