過程:設計Vue 3 - 知乎

原文連接: The process: Making Vue 3 – Increment: Frontend
翻譯: 谷歌翻譯
翻譯調整:Mario

在過去的一年中,Vue團隊一直在忙於開發Vue.js的下一個主版本,咱們但願在2020年上半年發佈該版本。(在撰寫本文時,這項工做仍在進行中)。一個新的Vue主版本的想法造成於2018年末,當時Vue 2的代碼庫已有兩年半的歷史了。在通用軟件的生命週期中聽起來可能並不長,但在此期間,前端環境發生了巨大變化。前端

有兩個主要的因素使咱們考慮開發新的(重寫版的)Vue主版本:首先,主流瀏覽器廣泛提供了新的JavaScript語言功能。其次,隨着時間的推移,當前代碼庫中的設計和體系結構問題已經暴露出來。vue

爲何要改寫

利用新的語言功能

隨着ES2 015 的標準化,JavaScript(正式稱爲ECMAScript,縮寫爲ES)得到了重大改進,主流瀏覽器終於​​開始爲這些新功能提供不錯的支持。一些改進尤爲爲咱們提供了極大提升Vue功能的機會。git

其中最值得注意的是Proxy,它容許框架攔截對象上的操做。Vue的一個核心功能是可以偵聽用戶定義狀態的更改並響應式更新DOM的能力。Vue 2經過使用getter和setter替換狀態對象上的屬性來實現這種響應能力。切換到Proxy將使咱們消除Vue的現有限制,例如沒法檢測到新的屬性添加並提供更好的性能。github

可是,Proxy是一種語言原生功能,不能在舊版瀏覽器中徹底Polyfill。爲了應用它,咱們知道咱們必須調整框架的瀏覽器支持範圍,這是一個非兼容性更新,只能在新的主版本中發佈。算法

解決架構問題

在維護Vue 2的過程當中,因爲現有架構的侷限性,咱們積累了許多難以解決的問題。例如,模板編譯器的編寫方式使適當的source-map支持很是具備挑戰性。一樣,雖然Vue 2從技術上容許構建針對非DOM平臺的更高級別的渲染器,但咱們必須分叉代碼庫並複製大量代碼,才能實現這一點。要在當前的代碼庫中解決這些問題,將須要進行大量風險較大的重構,這幾乎等同於重寫。後端

同時,因爲各類模塊內部與彷佛不屬於任何模塊的浮動代碼之間的隱式耦合,咱們積累了大量的技術債務,這使得孤立地理解代碼庫的一部分變得更加困難;而且咱們注意到,貢獻者不多會對進行重要的更改充滿信心。重寫將使咱們有機會牢記這些注意事項來從新考慮代碼組織。api

初始原型階段

咱們於2018年末開始對Vue 3進行原型設計,其初步目標是驗證這些問題的解決方案。在此階段,咱們主要致力於爲進一步發展奠基堅實的基礎。數組

切換到TYPESCRIPT

Vue 2最初是用純ES編寫的。在原型開發階段以後不久,咱們意識到類型系統對於這種規模的項目將很是有益。類型檢查極大地減小了在重構期間引入意外錯誤的機會,並有助於貢獻者更自信地進行重要的更改。咱們引入了Facebook的Flow類型檢查,由於它能夠逐步添加到現有的普通ES項目。Flow的確有所助益,可是咱們得到的收益並不如預期。尤爲是持續的非兼容性更新使升級變得痛苦。與TypeScript與Visual Studio Code的深度集成相比,集成開發環境的支持也不理想。瀏覽器

咱們還注意到,愈來愈多的用戶同時使用Vue和TypeScript。爲了支持它們的應用場景,咱們不得不單獨編寫和維護TypeScript聲明,只是由於源代碼使用了不一樣的類型系統。切換到TypeScript將使咱們可以自動生成聲明文件,從而減輕了維護負擔。前端框架

解耦內部封裝

咱們還採用了單一存儲庫的結構,整個框架由內部軟件包組成,每一個內部軟件包都具備本身的單獨API、類型定義和測試。咱們但願使這些模塊之間的依賴關係更加明確,從而使開發人員更容易閱讀、理解並進行全部更改。這是咱們努力下降項目貢獻壁壘並提升其長期可維護性的關鍵。

設置RFC流程

到2018年末,咱們有了一個使用新的響應式系統和虛擬DOM渲染器的工做原型。咱們已經驗證了咱們想要進行的內部體系結構改進,可是隻包含了面向公衆的API更改的雛形,如今是將它們變成具體設計的時候了。

咱們知道咱們必須儘早謹慎地作到這一點。Vue的普遍使用意味着非兼容性更新可能致使用戶大量遷移成本和潛在的生態系統碎片化。爲了確保用戶可以提供有關非兼容性更新的反饋,咱們在2019年初引入了RFC(徵求意見)流程。每一個RFC使用了一個由多個小節組成的模塊,各個小節分別聚焦於動機、設計細節、權衡和應用策略。因爲此過程是在GitHub存儲庫中進行的,提案是做爲請求請求提交的,所以討論會在註釋中天然展開。

做爲一個思想框架,它迫使咱們要充分考慮潛在變化的方方面面,讓咱們的社區參與設計過程,並提交深思熟慮出來的功能要求,該RFC的過程已被證實很是有用。

更快更小

性能對於前端框架相當重要。儘管Vue 2具備出色的性能,但經過嘗試新的渲染策略,重寫提供了進一步發展的機會。

克服虛擬DOM的瓶頸

Vue有一個至關獨特的渲染策略:它提供相似於HTML的模板語法,但將模板編譯爲可返回虛擬DOM樹的渲染函數。框架經過遞歸遍歷兩個虛擬DOM樹並比較每一個節點上的每一個屬性來肯定實際DOM的哪些部分須要更新。因爲現代JavaScript引擎執行了高級優化,所以這種略顯暴力的算法一般很快,可是更新仍然涉及許多沒必要要的CPU工做。當您查看包含大量靜態內容且只有少許動態綁定的模板時,效率低下尤爲明顯——整個虛擬DOM樹仍然須要遞歸遍歷以瞭解什麼地方發生了變化。

幸運的是,模板編譯步驟使咱們有機會對模板進行靜態分析並提取有關動態部分的信息。Vue 2經過跳過靜態子樹在某種程度上作到了這一點,可是因爲過於簡單的編譯器體系結構,難以實施更高級的優化。在Vue 3中,咱們使用適當的AST轉換管道重寫了編譯器,這使咱們可以以轉換插件的形式編寫編譯時優化。

有了新的體系結構,咱們但願找到一種渲染策略,以儘量減小開銷。一種選擇是放棄虛擬DOM並直接生成命令式DOM操做,但這將閹割了對高級用戶和庫做者極具價值的、直接編寫虛擬DOM渲染功能的能力。另外,這會是一個巨大的非兼容性更新。

其次,最好的方法是消除沒必要要的虛擬DOM樹遍歷和屬性比較,這在更新過程當中每每會帶來最大的性能開銷。爲了實現這一點,編譯器和運行時須要協同工做:編譯器分析模板並生成帶有優化提示的代碼,而運行時將拾取提示並在可能的狀況下采用快速路徑。這裏有三個主要的優化工做:

首先,在樹的層面上,咱們注意到,節點結構會在沒有模板指令(例如,v-if和v-for)動態改變節點結構的場合下徹底保持靜態。若是咱們將模板分爲由這些結構指令分隔的嵌套「塊」,每一個塊內的節點結構又會變得徹底靜態。當咱們更新一個塊內的節點時,咱們再也不須要遞歸遍歷樹——塊內的動態綁定可由一個扁平數組跟蹤。經過將咱們須要執行的樹遍歷量減小一個數量級,此優化可避免虛擬DOM的大部分開銷。\

其次,編譯器會主動檢測模板中的靜態節點、子樹甚至數據對象,並將其提高到生成代碼中的渲染函數以外。這樣能夠避免在每次渲染時從新建立這些對象,從而大大提升了內存使用率並減小了垃圾回收的頻率。\

第三,在元素級別,編譯器還會根據須要執行的更新類型爲具備動態綁定的每一個元素生成一個優化標誌。例如,具備動態類綁定和許多靜態屬性的元素將收到一個標誌,指示僅須要進行類檢查。運行時將獲取這些線索並採用專用的快速路徑。

CPU時間,即執行JavaScript計算所花費的時間,不包括瀏覽器DOM操做。

綜上所述,這些技術顯著提升了咱們的渲染更新標準,有時,使用Vue 3只須要Vue 2的CPU時間的不到十分之一。

最小化打包尺寸

框架的大小也會影響其性能。這是Web應用程序的一個獨有的關注點,由於資源須要即時下載,而且在瀏覽器解析必要的JavaScript以前,該應用程序將沒法與用戶交互,對於單頁應用程序尤爲如此。儘管Vue一直是相對輕量級的(gzip壓縮過的Vue 2的運行時大小約爲23KB),但咱們注意到了兩個問題:

首先,並非每一個人都使用框架的全部功能。例如,從未使用過渡功能的應用仍需付出與過渡相關的代碼下載、解析的代價。

其次,當咱們不斷添加新功能時,框架會無限制地增加。所以在進行是否要添加新功能的權衡時,會給予打包尺寸不相稱的權重。結果,咱們傾向於僅包含給大多數用戶使用的功能。

理想狀況下,用戶應該可以在構建時刪除未使用的框架功能的代碼——也稱爲「搖樹」——只爲他們使用的功能付出代價。這也將使咱們可以發佈一部分用戶以爲有用的功能,而不增長其餘用戶的負擔。

在Vue 3中,咱們經過將大多數全局API和內部輔助工具移至ES模塊來實現了這一目標。這使現代的打包器能夠靜態分析模塊依賴並刪除與未使用的模塊相關的代碼。模板編譯器還會生成搖樹友好的代碼,僅導入該功能實際上在模板中使用的輔助工具。

框架的某些部分永遠不會搖樹,由於它們對於任何類型的應用程序都是必不可少的。咱們將這些必不可少的部分的尺寸稱爲基準尺寸。儘管增長了許多新功能,但gzip壓縮的Vue 3的基準尺寸大約爲10KB不到Vue 2的一半。

知足規模需求

咱們還想提升Vue處理大型應用程序的能力。咱們最初的Vue設計着眼於較低的入門門檻與和緩的學習曲線。可是隨着Vue的普遍應用,咱們瞭解到愈來愈多的包含數百個模塊的項目須要,這些項目在其生命週期中由數十名開發人員維護。對於這種類型的項目,像TypeScript這樣的類型系統以及清晰地組織可重用代碼的能力相當重要,而Vue 2在這些領域的支持並不理想。

在設計Vue 3的早期階段,咱們嘗試經過提供對使用類編寫組件的內置支持來改善TypeScript集成。挑戰在於,咱們須要使類可用的許多語言功能(如類成員和裝飾器)仍然是提案,而且在正式加入JavaScript以前可能會發生變化。涉及的複雜性和不肯定性使咱們懷疑添加類API是否真的合理,由於它除了些微地提高了TypeScript集成以外沒有提供任何其餘好處。

咱們決定研究其餘解決擴展問題的方法。受React Hooks的啓發,咱們考慮過公開較低級別的響應能力和組件生命週期API,以實現一種更自由地編寫組件邏輯的方式,稱爲Composition API。無需經過指定一長串選項來定義組件,Composition API容許用戶像編寫函數同樣自由地表示、編寫和重用有狀態組件邏輯,同時還提供了出色的TypeScript支持。

咱們對這個想法感到很是興奮。儘管Composition API旨在解決一大類特定的問題,但從技術上講,能夠僅在編寫組件時才使用它。在該提案的初稿中,咱們有所超前地暗示咱們可能會在未來的版本中將現有的選項API替換爲Composition API。這致使社區成員的嚴重抵制,這給咱們上了關於如何清楚地傳達長期計劃和意圖,以及瞭解用戶需求的寶貴一課。在聽取了社區的反饋以後,咱們對提案進行了徹底的重寫,從而明確代表Composition API將對選項API構成增益和補充。修改後的提案獲得了更爲積極的接受,並收到了許多建設性的建議。

尋求平衡

在Vue超過一百萬的開發人員中,有隻有HTML/CSS基礎知識的初學者,有從jQuery遷移的專業人員,有從另外一個框架遷移過來的經驗豐富的老兵,有正在尋找前端解決方案的後端工程師以及處理規模軟件開發的軟件架構師。開發人員類型的多樣性與應用場景的多樣性相對應:一些開發人員可能但願在舊版應用程序上略增交互性,而另外一些開發人員可能從事快速交付的、維護需求有限的一次性項目,架構師可能不得不在整個生命週期中應對項目的龐大規模、漫長週期以及開發團隊頻繁變更。

在咱們尋求平衡各類折衷方案的同時,Vue的設計不斷受到這些需求的影響和啓發。Vue的口號,「漸進式框架」封裝了由此過程產生的分層API設計。初學者可使用CDN腳本、基於HTML的模板和直觀的選項API來輕鬆學習,而專家可使用功能齊全的CLI、渲染函數和Composition API來處理耗時費力的應用場景。

要實現咱們的願景,還有許多工做要作——最重要的是,更新支持庫、文檔和工具以確保順利遷移。在接下來的幾個月中,咱們將繼續努力,咱們火燒眉毛地想看到社區將經過Vue 3創造什麼。


關於做者

Evan You 是獨立的開源開發人員。他是Vue.js(當今使用最普遍的前端框架之一)的建立者和項目負責人。

@youyuxi

相關文章
相關標籤/搜索