[譯]尤雨溪:Vue3的設計過程

今日凌晨三點半左右,尤雨溪在他的微博上發表了一篇文章。 固然大佬是在另外一個時區,我們這的凌晨對應的應該是那個疫情最嚴重的時區的下午。前端

原文連接:increment.com/frontend/ma…vue

重構新版Vue.js的經驗教訓

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

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

爲何要重構

利用新的語言功能

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

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

可是,代理是語言自身的功能,不能在舊版瀏覽器中徹底polyfill。爲了利用它,咱們知道咱們必須調整框架的瀏覽器支持範圍,這是一個重大突破,只能在新的主要版本中發佈。瀏覽器

解決架構問題

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

同時,咱們以各類模塊的內部與浮動代碼之間隱式耦合的形式積累了隱患,而浮動代碼彷佛並不屬於任何地方。這使得孤立地理解代碼庫的一部分變得更加困難,而且咱們注意到,貢獻者不多會對重要的更改充滿信心。重構將使咱們有機會根據這些注意事項來從新考慮代碼組織。架構

初始原型階段

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

切換到TypeScript

Vue 2最初是用純JS編寫的。在原型開發階段以後不久,咱們意識到類型系統對於這種規模的項目將很是有幫助。類型檢查極大地減小了在重構期間引入意外錯誤的機會,並有助於貢獻者更自信的進行重要的更改。咱們經過Facebook的Flow類型檢查,由於它能夠逐步添加到現有的JS項目。Flow在必定程度上有所幫助,可是咱們並無從中得到太多收益。特別是不斷變化的需求使升級變得很痛苦。與TypeScript和Visual Studio Code的深度集成相比,Flow對集成開發環境的支持也不理想。

咱們還注意到,用戶愈來愈多地同時使用Vue和TypeScript。爲了支持它們的用例,咱們必須與使用不一樣類型系統分開創做和維護TypeScript聲明。切換到TypeScript將使咱們可以自動生成聲明文件,從而減輕了維護負擔。

解耦內部封裝

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

設置RFC流程

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

咱們知道咱們必須儘早而仔細地作到這一點。Vue的普遍使用意味着突破性變化可能致使用戶大量遷移成本和潛在的生態系統碎片化。爲了確保用戶可以提供有關重大更改的反饋,咱們在2019年初採用了RFC(徵求意見)流程。每一個RFC遵循一個模板,其中側重於動機,設計細節,權衡和採用策略。因爲此過程是在GitHub倉庫中進行的,提案是做爲請求請求提交的,所以討論會在註釋中天然展開。

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

更快更小

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

克服虛擬DOM的瓶頸

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

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

有了新的體系結構,咱們但願找到一種渲染策略,以儘量減小開銷。一種選擇是放棄虛擬DOM並直接生成命令式DOM操做,但這將消除直接編寫虛擬DOM渲染功能的能力,咱們發現這對高級用戶和庫做者很是有價值。另外,這將是一個巨大的突破性變化。

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

首先,在樹的層面上,咱們注意到,節點結構在沒有模板指令的時候是徹底靜態的(例如,v-if和v-for)。若是咱們將模板分爲動態的和靜態的「塊」,每一個塊內的節點結構再次變得徹底靜態。當咱們更新一個塊內的節點時,咱們再也不須要遞歸遍歷樹,由於咱們能夠在平面數組中跟蹤該塊內的動態綁定。經過將咱們須要執行的樹遍歷量減小一個數量級,從而節約了虛擬DOM的大部分開銷。

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

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

綜上所述,這些技術已顯著提升了咱們的渲染更新,運行Vue 3有時甚至會比Vue 2快上個十倍。

極小的尺寸

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

首先,並非每一個人都使用框架的全部功能。例如,從未使用過渡組件的應用仍會下載與過渡相關的代碼和而且花時間去解析它。

其次,當咱們添加新功能時,該框架會無限的變大。當咱們考慮新功能添加的時候,不得不考慮到尺寸的問題。所以,咱們傾向於框架僅包含大多數用戶會使用的功能。

理想狀況下,用戶應該可以在構建時刪除未使用的框架功能的代碼-也稱爲「Tree Shaking」 -只打包他們使用的代碼。這也將使咱們可以發佈一部分用戶會以爲有用的功能,而不會增長其他用戶的有效下載成本。

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

框架的某些部分永遠不會Tree Shaking,由於它們對於任何類型的應用程序都是必不可少的。咱們將這些必不可少的部分的度量標準稱爲基礎尺寸。儘管增長了許多新功能,但Vue 3的基準尺寸gzip後大約只有10KB ,甚至還不到Vue 2的一半。

知足規模需求

咱們還想提升Vue處理大型應用程序的能力。咱們最初的Vue設計着重於溫和的學習曲線。可是隨着Vue愈來愈普遍地被採用,咱們瞭解了更多有關項目需求的信息,這些項目包含數百個模塊,而且隨着時間的流逝由數十名開發人員維護。對於這些類型的項目,像TypeScript這樣的類型系統以及可複用代碼的能力相當重要,而Vue 2在這些領域的支持並不理想。

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

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

咱們對這個想法感到很是興奮。儘管Composition API旨在解決特定類別的問題,但從技術上講,僅在編寫組件時纔可使用它。在該提案的初稿中,咱們暗示咱們可能會在未來的版本中將現有的Options API替換爲Composition API。這致使社區成員的大量不滿,這爲咱們上了寶貴的一課,可使他們清楚地傳達長期計劃和意圖,以及瞭解用戶的需求。在聽取了咱們社區的反饋以後,咱們對提案進行了徹底的從新設計,從而明確代表Composition API將是對Options的補充和補充API。收到修改後的提案更加積極,並收到了許多建設性的建議。

尋求平衡

在Vue超過一百萬的開發人員中,有隻會HTML和CSS的基礎知識的初學者,有從jQuery遷移的老程序員,還有從另外一個框架遷移的前端,甚至還有正在尋找前端解決方案的後端工程師、以及大規模處理軟件的軟件架構師。一些開發人員可能但願在舊版應用程序上增長交互性,而另外一些開發人員可能須要敏捷開發但維護需求有限的一次性項目。在項目的整個生命週期中,可能不得不處理大型的,多年期的項目和一個波動的開發團隊。

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

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

相關文章
相關標籤/搜索