Vue3的簡單介紹
Vue3和Vue2的區別
源碼的組織方式
- 使用 TypeScript 重寫
首先為了提升代碼的可維護性,Vue3.0 拋棄了 Flow 類型注釋,而是全部采用了更加嚴格的 TypeScript 重寫,大型項目的開發都推薦使用類型化的語言,這樣可以在編碼的過程中幫助我們檢查類型化的問題,比如給函數傳參,如果類型不匹配,會有相應的類型提示。當然了,你可以不在你的項目中使用 TypeScript,而是使用 JavaScript,Vue3.0 也是完全支持的
- 使用了 monorepo 管理項目結構
monorepo 使用一個項目來管理多個包,把不同功能的代碼放在不同的包中去管理,這樣每個功能模塊的划分都很明確,模塊之間的依賴關系也很明確,並且每個功能模塊都可以單獨測試、單獨發布、單獨使用。
我們可以來看看源碼中的 packages 的目錄結構:
compiler-core 和平台無關的編譯器
compiler-dom 瀏覽器平台下的編譯器,依賴於 compiler-core
compiler-sfc sfc 的意思是 single-file-component 單文件組件,依賴於 compiler-core 和 compiler-dom
compiler-ssr 服務器端渲染的編譯器,依賴於 compiler-dom
reactivity 數據響應式系統,可以獨立使用
runtime-core 和平台無關的運行時
runtime-dom 針對瀏覽器的運行時,處理原生的 DOM 的 API 和 事件等
runtime-test 為測試編寫的輕量級的運行時,將 DOM 渲染成 js 對象,可以運行在所有的 js 環境里,可以驗證 DOM 是否渲染正確,還可以用來序列化 DOM、觸發 DOM 事件、記錄某次 DOM 更新操作
server-renderer 服務器端渲染
shared vue內部使用的一些內部api
size-check 在 tree-sharking 后檢查包的大小,這是一個私有的包,不會發布到 npm 上
template-explorer 在瀏覽器里運行的實時編譯組件,會輸出 render 函數
vue 構建完整版的 Vue,依賴於 compiler 和 runtime
Composition API
Vue3.0 的代碼雖然全部重寫,但是 90% 以上的 API 依然兼容 2.x。並且根據社區的反饋,增加了 Composition API,這是為了解決 Vue2.x 在開發大型項目時,處理超大組件使用 Options API 不好拆分和重用的問題。
1. 怎樣學習 Composition API?
學習 Composition API 的最好的方式就是查看官方的 RFC(Request For Comments),Vue2 升級到 Vue3 的大的變動就是通過 RFC 的機制去確認的,首先管方給出一些提案,然后收集社區的反饋並討論,最后確認。
RFC官方地址:https://github.com/vuejs/rfcs
Composition API 文檔:https://v3.vuejs.org/guide/composition-api-introduction.html#why-composition-api
2. Composition API 的設計動機
- Options API
Vue2.x 中使用 Options API,即包含一個描述組件選項(data、methods、props等)的對象,Options API 在開發復雜組件時,同一個功能邏輯的代碼會被拆分到不同選項。
下面我們來看一個案例
這個例子的代碼很簡單,就是當鼠標移動的時候,將鼠標的位置展示到頁面上。但是當我們想要添加新功能例如搜索功能的時候,我們就需要在多個 option 中添加我們的代碼,這就顯得有些麻煩。而且 Options API 很難提取一些公共代碼,雖然我們可以使用 mixin 去提取一些課重用的邏輯,但是 mixin 的使用也有很多問題,例如命名沖突和數據來源不清晰等問題。
- Composition API
Composition API 是 Vue3.0 新增的 API,是一組基於函數的 API,可以更靈活的組織組件的邏輯。
我們可以使用 Composition API 來重寫一下我們剛才的邏輯
上面的代碼我們可以看到,我們將所有重復的功能都封裝在 useMousePosition 這個函數中,當我們需要使用的時候,我們只需要在 setup 這個函數中去調用即可。這樣我們在查看某個邏輯的時候,只需要查看某個函數即可,不用再在各個 option 中來回查找了。
下面我們來一張 Options API 和 Composition API 的對比圖:
相同顏色的代表同樣的功能,我們可以看到 Options API 中同樣的功能被拆分成了不同的代碼塊,當組件的功能比較復雜的時候,同一邏輯的代碼被拆分到不同的位置,開發者就需要耗費一定的精力去組織這些邏輯。而 Composition API 中相同的邏輯都在同一個代碼塊,這樣在處理組件的時候會比較清晰。當然了 Composition API 只是 Vue3 新增的一組 API,你完全可以將 Options API 和 Composition API 結合起來使用,更加靈活的實現你的邏輯。
性能提升
Vue3.0 中使用 Proxy 重寫了響應式原理,並且對編譯器做了優化,重寫了虛擬 DOM,從而讓渲染和 update 的性能都有了大幅度的提升。另外官方介紹服務端渲染的性能也提升了兩到三倍。
1. 響應式系統升級
Vue2.x 中的響應式原理的核心是 Object.defineProperty,在 data 初始化的時候會遍歷所有成員對數據進行遞歸響應式處理,即使你沒有使用這個屬性的時候也會進行響應式處理;
而 Vue3.0 中使用 Proxy 對象重寫響應式系統,本身 Proxy 的性能就會比 Object.defineProperty 好,另外 Proxy 代理的對象可以攔截屬性的訪問、賦值、新增、刪除等操作,而且 Proxy 可以監聽數組的索引和 length 屬性。Proxy 代理的對象只有在訪問某個屬性的時候才會觸發代理,而不是像 Vue2.x 中初始化就遞歸處理所有屬性,使用 Proxy 默認就可以監聽動態添加的屬性,而在 Vue2.x 中只能使用 Vue.$set 方法去為對象動態添加屬性。所以 Vue3.0 中使用 Proxy 重寫響應式系統大大提升了整個框架的性能。
2. 編譯優化
我們先來通過一個組件來回顧 Vue2.x 的編譯執行過程
在 Vue2.x 中模板首先會編譯成 render 函數,這個過程一般是在構建的過程中完成的,在編譯的時候,會編譯靜態根節點和靜態節點,靜態根節點必須要求有一個靜態子節點,當組件發生變化時會觸發 Watcher,然后觸發 Watcher 的 update 函數,最終去執行虛擬 DOM 的 patch 操作,diff 的過程中會去遍歷比較所有的虛擬 DOM ,通過雙指針的算法去比較新舊 DOM,找到差異然后更新到真實 DOM 上。Vue2.x 中渲染的最小單位是組件,通過標記靜態根節點,在 diff 的過程中跳過靜態根節點,優化 diff 操作,但是靜態節點還是需要 diff,這個過程沒有被優化。
Vue3.0 為了提升性能,會標記和提升所有的靜態節點,diff 的時候只需要對比動態節點內容。另外在 Vue3.0 中引入了 Fragments 的特性,模板中不需要放一個唯一的根節點,模板中可以直接放文本內容,也可以放很多同級的 HTML 標簽,但是在 vscode 中需要升級你的 vetur 插件,否則編譯器會報錯。
下面我們使用官方提供的 explorer 插件來看看模板做了哪些優化
template:
template 被編譯后:
在 Vue3.0 中,如果模板的最外層沒有根節點的話,就會創建一個 Fragment 片段,操作 Fragment 的代價是很小的,相對於操作 DOM 耗費的性能非常小。
我們能看到 _hoisted_1、_hoisted_2、_hoisted_3 這樣的靜態節點被提升到了 render 的外層,這樣只有在頁面初始化的時候會創建一次,下次視圖重新渲染后直接引用靜態節點即可,這樣的靜態節點也不會參與 diff 的過程。
在編譯的后的圖示的第 18 行代碼,我們能看到后面的注釋是 9,9 是 Vue3.0 中引入的 patch-flag 的概念,代表了當前的文本和 props 是動態內容,同時還記錄的動態綁定的屬性是 id,當 diff 的時候,只會檢查動態綁定的文本和 id 屬性是否發生變化,這樣大大提升了虛擬 DOM diff 的性能。
在第 20 行代碼中,我們能看到有一個 cache,這個 cache 緩存了綁定的函數,在首次渲染的時候,會緩存這個函數,當再次調用這個函數的時候,會從緩存中讀取,這樣也避免了很多不必要的更新。
3. 源碼體積的優化
Vue3.0 中移除了一些不常用的 API,例如:inline-template、filter(我本人挺喜歡用的[手動狗頭])等。
Vue3.0 中對 Tree-shaking 的支持更好,Tree-shaking 依賴 ES Module,也就是 ES6 中的模塊化語法(import、export),通過編譯的靜態分析,找到沒有引入的模塊,在打包的時候過濾掉,讓打包的體積更小。Vue3.0 在設計之初,就考慮到了 Tree-shaking,內置的組件比如 transition、keep-live,內置的指令比如 v-model 都是按需引入的,另外 Vue3.0 的其他很多 API 都是支持 Tree-shaking 的,只有核心模塊和你使用了的才會打包,不使用就不會打包。
Vite
隨着 Vue3.0 的發布,管方也發布了新的構建工具 Vite,在開發和測試階段 Vite 不需要打包,而可以直接運行項目,大大提升了開發的效率。
我們再來介紹 Vite 之前,先來看看 ES Module 規范,因為 Vite 是基於 ES Module 規范的,如果你對 ES Module 規范不太了解,可以看看我之前寫的 模塊化開發。
現代瀏覽器基本都支持 ES Module (IE不支持【手動可惡!】),ES Module 有一些特性:
- 通過下面的方式加載模塊
<script type="module" src="..."></script>
- 支持模塊的 script 默認延遲加載
- 類似於 script 標簽設置 defer
- 在文檔解析完成后,觸發 DOMContentLoaded 事件前執行
Vite 之所以快,就是因為 Vite 直接使用 ES Module 去加載模塊,在開發模式下不需要打包就可以直接運行;而 Vue-cli 開發模式下必須對項目打包才可以運行。
Vite 特點
- 快速冷啟動
因為不需要打包,所以 Vite 可以利用 ES Module 的特性快速啟動項目
- 按需編譯
代碼是按需編譯的,因此只有代碼在當前運行環境需要加載的時候才會編譯,你不需要在開啟開發服務器的時候等待所有項目文件打包,當項目比較大的時候,這個節省的時間就比較明顯。
- 模塊熱更新
Vite 支持模塊熱更新,並且模塊熱更新的性能與模塊總數無關,無論你有多少模塊,hmr 的速度始終比較快。
Vite 在生產環境下使用 Rollup 打包,Rollup 基於瀏覽器原生的 ES Module 的方式打包,不需要使用 babel 把 import 轉換成 require,以及相應的輔助函數,因此打包的體積會比 webpack 打包的體積小很多。
Vite 創建項目
- Vite 創建 vue 項目
npm init vite-app <project-name>
cd <project-name>
npm install
npm run dev
- 基於模板創建項目
npm init vite-app -- template react
項目運行起來,我們可以右鍵查看網頁源代碼,就能看到 Vue 的入口文件 main.js 確實是通過 ES Module 方式引入的。
我們再來看看控制台 netWork 請求到的 App.vue 的解析結果
首先 Vite 開啟的服務器會劫持以 .vue 結尾的請求,會把以 .vue 結尾的文件解析成 js 文件,並將響應頭的 Content-Type 設置為 application/javascript,目的就是為了告訴瀏覽器,現在我給你發送的是一個 javascript 腳本
這次我們來看看 App.vue 又被請求了一次,不同的是這次帶了一個 type=template 的參數,這次請求服務器之后,服務器會把 App.vue 這個單文件組件通過 Vue3.0 中的模塊 compiler-sfc 給編譯成 render 函數,這基本就是 Vite 的工作原理。