關(guān)于 Vue3 這些知識(shí),你可能還不知道!
本文已經(jīng)作者 Matt Maribojoc 授權(quán)翻譯!
Vue 是目前前沿開發(fā)中最熱門的框架之一,到2019年每周的下載率翻了一番。2020 年初 Vue3的發(fā)布還會(huì)增加它的受歡迎程度。
Vue3 為開發(fā)人員提供了更多的控制,它使我們能夠精確地控制項(xiàng)目中發(fā)生的事情,從編寫定制的編譯和渲染方法到直 Vue reactivity API。
Vue3 使用 Proxy 來監(jiān)聽數(shù)據(jù)的變化
響應(yīng)性是 VueJS 的核心,數(shù)據(jù)必須具有依賴性,可以觀察并進(jìn)行更新以響應(yīng)任何更改,Vue2 使用 Object.defineProperty 創(chuàng)建 getter 和 setter 來實(shí)現(xiàn)響應(yīng)式。
使用Object.defineProperty有兩個(gè)主要問題,在官方文檔中都提到過:Vue 不能檢測(cè)數(shù)組和對(duì)象的變化。
對(duì)于對(duì)象
Vue 無法檢測(cè) property 的添加或移除。由于 Vue 會(huì)在初始化實(shí)例時(shí)對(duì) property 執(zhí)行 getter/setter 轉(zhuǎn)化,所以 property 必須在 data 對(duì)象上存在才能讓 Vue 將它轉(zhuǎn)換為響應(yīng)式的。
對(duì)于數(shù)組
Vue 不能檢測(cè)以下數(shù)組的變動(dòng):
- 當(dāng)你利用索引直接設(shè)置一個(gè)數(shù)組項(xiàng)時(shí),例如:vm.items[indexOfItem] = newValue
- 當(dāng)你修改數(shù)組的長(zhǎng)度時(shí),例如:vm.items.length = newLength
舉個(gè)例子:
- var vm = new Vue({
- data: {
- items: ['a', 'b', 'c']
- }
- })
- vm.items[1] = 'x' // 不是響應(yīng)性的
- vm.items.length = 2 // 不是響應(yīng)性的
為什么使用 Proxy ?
Vue3 的解決方案是使用基于Proxy的觀察者模式來解決 Vue2 響應(yīng)上的一些限制。
新舊系統(tǒng)之間的主要區(qū)別在于,在Vue2中,Object.defineProperty會(huì)修改原始數(shù)據(jù),而Proxy則不會(huì),Proxy 虛擬化目標(biāo)數(shù)據(jù)并設(shè)置不同的處理程序(稱為target),這些處理程序通過getters和setter攔截?cái)?shù)據(jù)。
Vue3 意味著我們無需使用vm.$set來讓數(shù)據(jù)動(dòng)態(tài)的響應(yīng),同時(shí)也解決 vue2 操作數(shù)組無法響應(yīng)的問題。
正如尤雨溪大哥所總結(jié)的那樣,基于代理可以支持:
- 檢測(cè)屬性 添加/刪除
- 檢測(cè)數(shù)組 index/length 的變化
- 支持Map,Set, WeakMap 和WeakSet
Composition API
這是到目前為止 Vue3 最大的一個(gè)變化,它有助于代碼的組織和重用性。
目前,在Vue中我們使用是Options API。Options API按屬性組織代碼:data,computed,methods等。
這是一個(gè)非常直觀的方式,但維護(hù)一些復(fù)雜組件變得非常困難。單個(gè)功能的代碼通常在相隔數(shù)百行的多個(gè)地方拋出。
可維護(hù)性和可讀性成為主要問題。
接著,我們快速了解一下Composition API的工作原理。
- import { reactive, computed } from 'vue'
- export default {
- setup() {
- let state = reactive({
- input: '',
- groceries: [],
- groceriesLeft: computed(() => { groceries.length })
- })
- function addGrocery() {
- state.groceries.push(state.input)
- state.input = ''
- }
- function deleteGrocery(index) {
- state.groceries.splice(index, 1);
- }
- return {
- state,
- addGrocery,
- deleteGrocery
- }
- }
- }
我們來分析一下,上面的過程 ??
- import { reactive, computed } from 'vue'
Vue Composition API公開了Vue中的許多核心功能,比如reactive和組件方法,所以,我們需要導(dǎo)入它們。
- export default {
- setup() {
setup()方法的引入是 Vue3 中最大的變化之一。從本質(zhì)上講,它使我們能夠確定傳遞回模板的內(nèi)容,無論返回什么,都可以在模板中訪問。
我們可以在 setup 里面設(shè)置reactive 數(shù)據(jù),生命周期,計(jì)算屬性,定義的方法并返回我們想要的任何東西。
- let state = reactive({
- input: '',
- groceries: [],
- groceriesLeft: computed(() => { groceries.length })
- })
通過以反應(yīng)式方法包裝所有這些數(shù)據(jù),所有數(shù)據(jù)在內(nèi)部都將變?yōu)榉磻?yīng)式。此狀態(tài)模式來自Composition API文檔。
reactive() 函數(shù)接收一個(gè)對(duì)象作為參數(shù),并返回一個(gè)代理對(duì)象,所有數(shù)據(jù)在內(nèi)部都將變?yōu)轫憫?yīng)式的。
需要注意的一點(diǎn)是我們聲明groceriesLeft變量的方式。它是一個(gè)計(jì)算的屬性,并在setup()方法中定義,不再單獨(dú)拎出來聲明。
- function addGrocery() {
- state.groceries.push(state.input)
- state.input = ''
- }
- function deleteGrocery(index) {
- state.groceries.splice(index, 1)
- }
函數(shù)聲明倒是沒啥變化,區(qū)別一點(diǎn)是由于所有響應(yīng)數(shù)據(jù)都存儲(chǔ)在state 對(duì)象中,因此我們必須使用狀態(tài)對(duì)象進(jìn)行訪問,但這不是Vue3特有的。
- return {
- state,
- addGrocery,
- deleteGrocery
- }
最后,我們想從setup()方法返回這些函數(shù),這樣聲明的數(shù)據(jù)和方法就可在模板內(nèi)部訪問。
初次引入此方法時(shí),Vue 社區(qū)中存在許多反對(duì),因?yàn)殚_發(fā)者不希望被迫編寫這種新的方式。但是,這個(gè)也可選的,就是說我們?nèi)匀豢梢允褂?vue2 方式來做。
現(xiàn)在可以在Vue中使用 Suspense
Suspense是React的一個(gè)功能,現(xiàn)已在Vue3中引入。Suspense 讓組件“等待”某個(gè)異步操作,直到該異步操作結(jié)束即可渲染。
當(dāng)我們想要異步加載setup()方法中的內(nèi)容時(shí),這很有用。簡(jiǎn)而言之,只需知道 setup 方法可以像其他方法一樣被設(shè)置為異步的。
如果我們要在等待組件獲取數(shù)據(jù)并解析時(shí)顯示“正在拼了命的加載…”之類的內(nèi)容,則只需三個(gè)步驟即可實(shí)現(xiàn)Suspense。
- 將異步組件包裝在標(biāo)記中
- 在我們的 Async 組件的旁邊添加一個(gè)兄弟姐妹,標(biāo)簽為。
- 將兩個(gè)組件都包裝在組件中
使用插槽,Suspense 將渲染后備內(nèi)容,直到默認(rèn)內(nèi)容準(zhǔn)備就緒。然后,它將自動(dòng)切換以顯示我們的異步組件。
- <Suspense>
- <template #default>
- <article-info/>
- </template>
- <template #fallback>
- <div>正在拼了命的加載…</div>
- </template>
- </Suspense>
Fragment
在Vue 3中,我們可以期待的另一個(gè)令人興奮的補(bǔ)充是Fragment。你可能會(huì)問,什么是碎片?如果你創(chuàng)建一個(gè)Vue組件,那么它只能有一個(gè)根節(jié)點(diǎn)。這意味著不能創(chuàng)建這樣的組件
- <template>
- <div>Hello</div>
- <div>World</div>
- </template>
原因是代表任何Vue組件的Vue實(shí)例需要綁定到一個(gè)單一的DOM元素中。唯一可以創(chuàng)建一個(gè)具有多個(gè)DOM節(jié)點(diǎn)的組件的方法就是創(chuàng)建一個(gè)沒有底層Vue實(shí)例的功能組件。
結(jié)果發(fā)現(xiàn)React社區(qū)也遇到了同樣的問題。他們想出的解決方案是一個(gè)名為 Fragment 的虛擬元素。它看起來差不多是這樣的:
- class Columns extends React.Component {
- render() {
- return (
- <React.Fragment>
- <td>Hello</td>
- <td>World</td>
- </React.Fragment>
- );
- }
- }
盡管Fragment看起來像一個(gè)普通的DOM元素,但它是虛擬的,根本不會(huì)在DOM樹中呈現(xiàn)。這樣我們可以將組件功能綁定到一個(gè)單一的元素中,而不需要?jiǎng)?chuàng)建一個(gè)多余的DOM節(jié)點(diǎn)。
目前你可以在Vue 2中使用vue-fragments庫來使用Fragments,而在Vue 3中,你將會(huì)在開箱即用!
Portals
Portals是一種特殊的組件,目的是在當(dāng)前組件之外渲染某些內(nèi)容。這也是React中原生實(shí)現(xiàn)的功能之一。下面是 React 文檔中關(guān)于portals的說法。
Portals 提供了一種第一流的方式,可以將子節(jié)點(diǎn)渲染到父組件的DOM層次結(jié)構(gòu)之外的DOM節(jié)點(diǎn)中。
這是一種非常好的處理modals、彈出窗口和一般要出現(xiàn)在頁面頂部的組件的方式。通過使用portals,你可以確保沒有任何一個(gè)主組件的CSS規(guī)則會(huì)影響到你想要顯示的組件,并且免除了你用z-index做討厭的黑客的麻煩。
下面是Portal-Vue文檔中的示例屏幕截圖和代碼。
對(duì)于每一個(gè)Portals,我們需要指定它的目標(biāo)目的地,在那里,Portals內(nèi)容將被渲染。
- <template>
- <div>
- <div>
- <p>
- The content below this paragraph is
- rendered in the right/bottom (red) container by PortalVue
- </p>
- <Portal to="right-basic">
- <p class="red">
- This is content from the left/top container (green).
- The cool part is, it works across components,
- so you can send your content anywhere!
- </p>
- </Portal>
- </div>
- <PortalTarget name="right-basic"></PortalTarget>
- </div>
- </template>
Vue3 優(yōu)化了渲染
內(nèi)部測(cè)試表明,Vue3中的模板樣式比Vue2快約120%。
有兩個(gè)關(guān)鍵的優(yōu)化提高Vue3渲染速度:
- Block Tree 優(yōu)化
- 提升靜態(tài)節(jié)點(diǎn)樹
我們進(jìn)一步詳細(xì)介紹一下。
Block Tree 優(yōu)化
使用虛擬DOM有一個(gè)瓶頸,因?yàn)槊總€(gè)組件都必須跟蹤其依賴關(guān)系。監(jiān)聽這些依賴關(guān)系速度會(huì)變慢很多,因?yàn)樗f歸地檢查整個(gè)元素樹。
Vue團(tuán)隊(duì)注意到的一件事是,在組件中,節(jié)點(diǎn)的大部分結(jié)構(gòu)都是靜態(tài)的。而且,如果某個(gè)節(jié)實(shí)際上是動(dòng)態(tài)的(由于v-if或v-for指令),則其中的許多內(nèi)容都是靜態(tài)的。
使用此想法,Vue3將模板分為靜態(tài)部分與動(dòng)態(tài)部分?,F(xiàn)在,渲染器知道哪些節(jié)點(diǎn)是動(dòng)態(tài)的,它不會(huì)浪費(fèi)時(shí)間檢查靜態(tài)節(jié)點(diǎn)的變化。
這大大減少了需要被動(dòng)監(jiān)視的元素?cái)?shù)量。
結(jié)合所有這些節(jié)點(diǎn)可創(chuàng)建一個(gè)Block Tree或一個(gè)基于指令(v-if,v-for)分為節(jié)點(diǎn)塊的模板。
在 Block Tree 中,每個(gè)節(jié)點(diǎn)具有:
- 完全靜態(tài)的節(jié)點(diǎn)結(jié)構(gòu)
- 不需要監(jiān)聽的靜態(tài)內(nèi)容
- 可以存儲(chǔ)在數(shù)組中的動(dòng)態(tài)節(jié)點(diǎn)
這消除了對(duì)每個(gè)元素進(jìn)行遞歸檢查的需要,從而大大改善了運(yùn)行時(shí)間。
靜態(tài)樹提升(Static Tree Hoisting)
使用靜態(tài)樹提升,這意味著 Vue 3 的編譯器將能夠檢測(cè)到什么是靜態(tài)的,然后將其提升,從而降低了渲染成本。它將能夠跳過 patching 整棵樹。
這大大減少了虛擬DOM的工作,并節(jié)省了大型項(xiàng)目開銷,主要是垃圾收集。
支持 Typescript
另一個(gè)變化是Vue代碼庫將使用Typescript重寫,這個(gè)對(duì)于前端來說,又得去學(xué)習(xí) TS 才能更好的上手 Vue3。
所以 Vue 也提供了兩種選擇給我:如果你想要Typescript,那就用。如果不想,那就用 Vue2 的方式。
Typescript 規(guī)范了 JS 變量中類型信息。多長(zhǎng)遠(yuǎn)來看,這有助于我們對(duì)項(xiàng)目的維護(hù)。
超輕量級(jí)
目前,VueJS已經(jīng)很小(20kb gzip壓縮)。但是,Vue 團(tuán)隊(duì)面臨一個(gè)問題:新特性增加了每個(gè)用戶的捆綁包大小,不管他們是否使用它。
為了解決這個(gè)問題,Vue3 更加徹底的模塊化。這樣做增加需要開發(fā)的導(dǎo)入模塊數(shù)量,但可確保我們項(xiàng)目中引入未使用的庫。
由于 tree shaking ,Vue 3 的估計(jì)大小大約壓縮了10 kb。當(dāng)然,許多庫都需要被導(dǎo)入。
準(zhǔn)備好了嗎
如果你是 Vue 的使用者,那么很明顯,Vue3 中的更新將使它變得更加實(shí)用且功能強(qiáng)大。
從渲染優(yōu)化到幫助開發(fā)人員編寫更具可讀性/可維護(hù)性的代碼,Vue3改善Vue2遇到的許多痛點(diǎn)。
Vue3 已經(jīng)正式發(fā)布了,你準(zhǔn)備好了嗎,快來上手學(xué)習(xí)吧!
作者:Matt Maribojoc 譯者:前端小智 來源:medium
原文:https://medium.com/swlh/what-you-need-to-know-about-vue3-in-2020-b36a2feb5dad
本文轉(zhuǎn)載自微信公眾號(hào)「大遷世界」,可以通過以下二維碼關(guān)注。轉(zhuǎn)載本文請(qǐng)聯(lián)系大遷世界公眾號(hào)。