Vue3.0新特性
Vue3.0新特性
Vue3.0
的設計目標可以概括為體積更小、速度更快、加強TypeScript
支援、加強API
設計一致性、提高自身可維護性、開放更多底層功能。
描述
從Vue2
到Vue3
在一些比較重要的方面的詳細對比。
生命周期的變化
Vue2 -> Vue3
beforeCreate -> setup
created -> setup
beforeMount -> onBeforeMount
mounted -> onMounted
beforeUpdate -> onBeforeUpdate
updated -> onUpdated
beforeDestroy -> onBeforeUnmount
destroyed -> onUnmounted
activated -> onActivated
deactivated -> onDeactivated
errorCaptured -> onErrorCaptured
renderTracked -> onRenderTracked
renderTriggered -> onRenderTriggered
在這裡主要是增加了setup
這個生命周期,而其他的生命周期都是以API
的形式調用,實際上隨著Composition API
的引入,我們訪問這些鉤子函數的方式已經改變,我們所有的生命周期都應該寫在setup
中,此方法我們應該實現大多數組件程式碼,並處理響應式,生命周期鉤子函數等。
import { onBeforeMount,
onMounted,
onBeforeUpdate,
onUpdated,
onBeforeUnmount,
onUnmounted,
onActivated,
onDeactivated,
onErrorCaptured,
onRenderTracked,
onRenderTriggered
} from "vue";
export default {
setup() {
onBeforeMount(() => {
// ...
})
onMounted(() => {
// ...
})
onBeforeUpdate(() => {
// ...
})
onUpdated(() => {
// ...
})
onBeforeUnmount(() => {
// ...
})
onUnmounted(() => {
// ...
})
onActivated(() => {
// ...
})
onDeactivated(() => {
// ...
})
onErrorCaptured(() => {
// ...
})
onRenderTracked(() => {
// ...
})
onRenderTriggered(() => {
// ...
})
}
}
使用proxy代替defineProperty
Vue2
是通過數據劫持的方式來實現響應式的,其中最核心的方法便是通過Object.defineProperty()
來實現對屬性的劫持,該方法允許精確地添加或修改對象的屬性,對數據添加屬性描述符中的getter
與setter
存取描述符實現劫持。Vue2
之所以只能兼容到IE8
主要就是因為defineProperty
無法兼容IE8
,其他瀏覽器也會存在輕微兼容問題。
var obj = { __x: 1 };
Object.defineProperty(obj, "x", {
set: function(x){ console.log("watch"); this.__x = x; },
get: function(){ return this.__x; }
});
obj.x = 11; // watch
console.log(obj.x); // 11
Vue3
使用Proxy
實現數據劫持,Object.defineProperty
只能監聽屬性,而Proxy
能監聽整個對象,通過調用new Proxy()
,可以創建一個代理用來替代另一個對象被稱為目標,這個代理對目標對象進行了虛擬,因此該代理與該目標對象表面上可以被當作同一個對象來對待。代理允許攔截在目標對象上的底層操作,而這原本是Js
引擎的內部能力,攔截行為使用了一個能夠響應特定操作的函數,即通過Proxy
去對一個對象進行代理之後,我們將得到一個和被代理對象幾乎完全一樣的對象,並且可以從底層實現對這個對象進行完全的監控。Proxy
對象是ES6
引入的新特性,Vue3
放棄使用了Object.defineProperty
,而選擇了使用更快的原生Proxy
,即是在兼容性方面更偏向於現代瀏覽器。
var target = {a: 1};
var proxy = new Proxy(target, {
set: function(target, key, value, receiver){
console.log("watch");
return Reflect.set(target, key, value, receiver);
},
get: function(target, key, receiver){
return target[key];
}
});
proxy.a = 11; // watch
console.log(target); // { a: 11 }
diff演算法的提升
diff
演算法的基礎是Virtual DOM
,Virtual DOM
是一棵以JavaScript
對象作為基礎的樹,每一個節點稱為VNode
,用對象屬性來描述節點,實際上它是一層對真實DOM
的抽象,最終可以通過渲染操作使這棵樹映射到真實環境上,簡單來說Virtual DOM
就是一個Js
對象,用以描述整個文檔。
Vue2
框架通過深度遞歸遍歷新舊兩個虛擬DOM
樹,並比較每個節點上的每個屬性,來確定實際DOM
的哪些部分需要更新,由於現代JavaScript
引擎執行的高級優化,這種有點暴力的演算法通常非常快速,但是DOM
的更新仍然涉及許多不必要的CPU
工作。
在這裡引用尤大的描述,為了實現這一點,編譯器和運行時需要協同工作:編譯器分析模板並生成帶有優化提示的程式碼,而運行時儘可能獲取提示並採用快速路徑,這裡有三個主要的優化:
- 首先,在
DOM
樹級別,我們注意到,在沒有動態改變節點結構的模板指令(例如v-if
和v-for
)的情況下,節點結構保持完全靜態,如果我們將一個模板分成由這些結構指令分隔的嵌套塊,則每個塊中的節點結構將再次完全靜態,當我們更新塊中的節點時,我們不再需要遞歸遍歷DOM
樹,該塊內的動態綁定可以在一個平面數組中跟蹤,這種優化通過將需要執行的樹遍歷量減少一個數量級來規避虛擬DOM
的大部分開銷。 - 其次,編譯器積極地檢測模板中的靜態節點、子樹甚至數據對象,並在生成的程式碼中將它們提升到渲染函數之外,這樣可以避免在每次渲染時重新創建這些對象,從而大大提高記憶體使用率並減少垃圾回收的頻率。
- 第三,在元素級別,編譯器還根據需要執行的更新類型,為每個具有動態綁定的元素生成一個優化標誌,例如具有動態類綁定和許多靜態屬性的元素將收到一個標誌,提示只需要進行類檢查,運行時將獲取這些提示並採用專用的快速路徑。
TypeScript的支援
Vue2
中使用的都是Js
,其本身並沒有類型系統這個概念,現如今TypeScript
異常火爆,對於規模很大的項目,沒有類型聲明,後期維護和程式碼的閱讀都是頭疼的事情,雖然Vue2
實際上也是可以使用TS
的,但是支援並不算特別完美,此外Vue2
的源碼也是使用Facebook
的Flow
做類型檢查。
最終Vue3
借鑒了React Hook
實現了更自由的編程方式,提出了Composition API
,Composition API
不需要通過指定一長串選項來定義組件,而是允許用戶像編寫函數一樣自由地表達、組合和重用有狀態的組件邏輯,同時提供出色的TypeScript
支援。
打包體積變化
Vue2
官方說明運行時打包23k
,但這只是沒安裝依賴的時候,隨著依賴包和框架特性的增多,有時候不必要的,未使用的程式碼文件都被打包了進去,所以後期項目大了,打包文件會特別多還很大。
在Vue3
中,通過將大多數全局API
和內部幫助程式移動到JavaScript
的module.exports
屬性上實現這一點,這允許現代模式下的module bundler
能夠靜態地分析模組依賴關係,並刪除與未使用的module.exports
屬性相關的程式碼,模板編譯器還生成了對Tree Shaking
搖樹優化友好的程式碼,只有在模板中實際使用某個特性時,該程式碼才導入該特性的幫助程式,儘管增加了許多新特性,但Vue3
被壓縮後的基準線大小約為10KB
,不到Vue2
的一半。
非兼容變更
Vue3
相對於Vue2
的非兼容的變更概括,詳情可以查閱//v3.cn.vuejs.org/guide/migration/introduction.html
。
全局API
- 全局
Vue API
已更改為使用應用程式實例。 - 全局和內部
API
已經被重構為可tree-shakable
。
模板指令
- 組件上
v-model
用法已更改,替換v-bind.sync
。 <template v-for>
和非v-for
節點上key
用法已更改。- 在同一元素上使用的
v-if
和v-for
優先順序已更改。 v-bind="object"
現在排序敏感。v-on:event.native
修飾符已移除。v-for
中的ref
不再註冊ref
數組。
組件
- 只能使用普通函數創建功能組件。
functional
屬性在SFC
單文件組件<template>
和functional
組件選項被拋棄。- 非同步組件現在需要
defineAsyncComponent
方法來創建。 - 組件事件現在需要在
emits
選項中聲明。
渲染函數
- 渲染函數
API
改變。 $scopedSlots property
已刪除,所有插槽都通過$slots
作為函數暴露。$listeners
被移除或整合到$attrs
。$attrs
現在包含class and style attribute
。
自定義元素
- 自定義元素白名單現在已經在編譯時執行。
- 對特殊的
is prop
的使用只嚴格限制在被保留的<component>
標記中。
其他小改變
destroyed
生命周期選項被重命名為unmounted
。beforeDestroy
生命周期選項被重命名為beforeUnmount
。default prop
工廠函數不再可以訪問this
上下文。- 自定義指令
API
已更改為與組件生命周期一致。 data
選項應始終被聲明為一個函數。- 來自
mixin
的data
選項現在為淺合併。 Attribute
強制策略已更改。- 一些過渡
class
被重命名。 <TransitionGroup>
不再默認渲染包裹元素。- 當偵聽一個數組時,只有當數組被替換時,回調才會觸發,如果需要在變更時觸發,則需要指定
deep
選項。 - 沒有特殊指令的標記
v-if/else-if/else
、v-for
、v-slot
的<template>
現在被視為普通元素,並將生成原生的<template>
元素,而不是渲染其內部內容。 - 在
Vue2
中,應用根容器的outerHTML
將替換為根組件模板,如果根組件沒有模板/渲染選項,則最終編譯為模板,Vue3
現在使用應用容器的innerHTML
,這意味著容器本身不再被視為模板的一部分。
移除API
keyCode
支援作為v-on
的修飾符。$on
、$off
和$once
實例方法- 過濾器方法,建議用計算屬性或方法代替過濾器。
- 內聯模板
attribute
。 $children
實例property
。$destroy
實例方法,用戶不應再手動管理單個Vue
組件的生命周期。
示例
Vue3
的組件簡單示例,可查看在線示例//codesandbox.io/s/c1437?file=/src/App.vue
。
<template>
<div>
<div>{{ msg }}</div>
<div>count: {{ count }}</div>
<div>double-count: {{ doubleCount }}</div>
<button @click="multCount(3)">count => count*3</button>
</div>
</template>
<script>
import { onMounted, reactive, computed } from "vue";
export default {
name: "App",
components: {},
setup: () => {
/* 定義data */
const data = reactive({
msg: "Hello World",
count: 1,
});
/* 處理生命周期 */
onMounted(() => {
console.log("Mounted");
});
/* 處理computed */
const computeds = {
doubleCount: computed(() => data.count * 2),
};
/* 定義methods */
const methods = {
multCount: function (n) {
data.count = data.count * n;
},
};
/* 返回數據 */
return Object.assign(data, computeds, methods);
},
};
</script>
<style>
</style>
每日一題
//github.com/WindrunnerMax/EveryDay
參考
//zhuanlan.zhihu.com/p/92143274
//www.jianshu.com/p/9d3ddaec9134
//zhuanlan.zhihu.com/p/257044300
//juejin.cn/post/6867123074148335624
//juejin.cn/post/6892295955844956167
//segmentfault.com/a/1190000024580501
//v3.cn.vuejs.org/guide/migration/introduction.html