15.1 源碼組織方式
- 采用 TypeScript 的方式重寫
- 為了提升代碼的可維護性,Vue 3.x 的源碼全部采用 TypeScript 編寫
- 大型項目的開發都推薦使用類型化的語言,在編碼的過程當中幫我們檢查類型的問題
- 使用 Monorepo 管理項目結構
- 把獨立的功能子產品都提取到不同的包中,每個功能子產品之間的劃分明确,子產品之間的依賴關系也明确
- 每個功能子產品都可以單獨測試、單獨釋出以及單獨使用
packages 目錄結構
- packages 目錄下都是獨立發行的包,可以獨立使用
- compiler - xxx 跟編譯相關的代碼
- compiler-core 與平台無關的編譯器
- compiler-dom 浏覽器平台下的編譯器,依賴于 compiler-core
- compiler-sfc 用來編譯單檔案元件,依賴于 compiler-core 與 compiler-dom
- compiler-ssr 服務端渲染的編譯器,依賴于 compiler-dom
- reactivity 資料響應式系統,可以獨立使用
- runtime - xxx 跟運作時相關的代碼
- runtime-core 與平台無關的運作時
- runtime-dom 針對浏覽器的運作時,處理 原生DOM 的 API、事件等
- runtime-test 專門為測試編寫的輕量級的運作時,這個運作時渲染出來的DOM樹其實是一個JS對象,可以運作在所有的運作環境
- server-renderer 用于服務端渲染
- shared Vue内部使用的一些公共 API
- size-check 私有的包,不會發不到 Npm,作用是在 Tree-shaking 之後檢查包的大小
- template-explorer 在浏覽器運作的實時編譯元件,會輸出render函數,README.md 提供線上通路位址
- vue 用來建構完整版的 Vue,依賴于 compiler 和 runtime
- compiler - xxx 跟編譯相關的代碼
15.2 不同的建構版本
Vue 3.x 在建構時與 Vue 2.x 類似都建構了不同的版本
和 Vue 2.x 不同的是,Vue 3.x 中不再建構 UMD 子產品化的方式,因為其會讓代碼有更多的備援,它要支援多種子產品化的方式
Vue 3.x 的建構版本中把 cjs、ESModule 和 自執行函數 的方式分别打包到了不同的檔案中
- packages/vue 存放了 Vue 3.x 中的所有建構版本
- cjs 也就是 Common JS 的子產品化方式,此處兩個檔案都是完整版的 Vue 包含運作時和編譯器
- vue.cjs.js 開發版本,代碼未被壓縮
- vue.cjs.prod.js 生産版本,代碼被壓縮過
- global 全局的意思,這四個檔案都可以在浏覽器直接通過 script 标簽導入,會增加一個全局的 Vue 對象
- vue.global.js 完整版的 Vue,開發版本
- vue.global.prod.js 完整版的 Vue,生産版本
- vue.runtime.global.js 隻包含運作時,開發版本
- Vue.runtime.global.prod.js 隻包含運作時,生産版本
- browser 都包含 ES Module,浏覽器的原生子產品化的方式,在浏覽器中可以直接通過
導入<script type="module" src=""></script>
- vue.esm-browser.js 完整版的 ESM,開發版本
- vue.esm-browser.prod.js 完整版的 ESM,開發版本
- vue.runtime.esm-browser.js 隻包含運作時,開發版本
- vue.runtime.esm-browser.prod.js 隻包含運作時,開發版本
- bundler 這兩個檔案沒有打包所有的代碼,它們需要配合打包工具使用,使用 ES Module 的子產品化方式,内部通過 import 導入 runtime-core
- Vue.esm-bundler.js 完整版,内部導入 runtime、compiler,也就是編譯器
- vue.runtime.esm-bundler.js 使用腳手架建立的項目中的預設導入,這個檔案隻導入了運作時,它是 Vue 的最小版本,在項目開發完畢後重新打包時打包我們使用到的代碼
15.3 Composition API
Vue 3.x 雖然代碼全部重寫,但是 90% 以上的 API 依然相容 2.x,并且根據社群的回報增加了 Composition API 即 組合API
- RFC(Request For Comments)
- https://github.com/vuejs/rfcs
- Composition API RFC
- https://composition-api.vuejs.org
設計動機
它是用來解決 2.x 在開發大型項目時遇到超大元件使用 Options API 不好拆分重用的問題
- Options API
- 包含一個描述元件選項(data、methods、props等)的對象
- Options API 開發複雜元件,同一個功能邏輯的代碼被拆分到不同選項
- Options API Demo
- Composition API
- Vue.js 3.x 新增的一組 API
- 一組基于函數的 API
- 可以更靈活的組織元件的邏輯
- Composition API Demo
相對于 Options API 這樣做的好處:檢視某個邏輯時隻需關注具體的函數即可,目前的邏輯代碼都封裝在函數内部,不像 Options API 時擷取滑鼠位置的邏輯代碼分散在不同的位置,檢視這部分代碼還需要上下拖動滾動條
再來看一下官方提供的這張圖來感受 Options API 和 Composition API 的差別:
Options API 中同一色塊代表同一功能,我們可以看到相同功能的代碼被拆分在不同位置,當元件的功能比較複雜,統一邏輯的代碼被拆分在不同位置,我們就需要不停拖動滾動條來找到我們需要的代碼,且不友善提取重用代碼
Composition API 也是使用相同色塊代表同一功能,我們可以看到相同功能的代碼不需要拆分,有利于對代碼的提取和重用
在 Vue.js 3.x 中你即可以使用 Options API,也可以使用 Composition API
15.4 性能提升
- 響應式系統更新
- 編譯更新
- 源碼體積的優化
在性能方面 Vue.js 3.x 又大幅度提升,使用代理對象 Proxy 重寫了響應式的代碼并且對編譯器做了優化,重寫了虛拟DOM,進而讓渲染和Update的性能都有了大幅度的提升
另外,官方介紹服務端渲染的性能也提升兩到三倍
響應式系統的更新
- Vue.js 2.x 中響應式系統的核心 defineProperty
- 初始化時周遊 data 中的所有成員,通過 defineProperty 把對象的屬性轉換成 getter 和 setter,如果 data 中的屬性又是對象的話,需要遞歸處理每一個子對象的屬性。這些都是初始化時進行的,如果你未使用這些屬性也會進行響應式的處理
- Vue.js 3.x 中使用 Proxy 對象重寫響應式系統
- Proxy 的性能本身就比 defineProperty 好,且代理對象可以攔截屬性的通路、指派、删除等操作,不需要初始化時周遊所有的屬性,如果有多層屬性嵌套隻有通路某個屬性時才會遞歸處理下一級屬性
- 使用 Proxy 對象預設可以監聽動态新增的屬性,而 Vue.js 2.x 想要動态添加響應式屬性需要調用 Vue.set 方法來處理
- Vue.js 2.x 監聽不到屬性的删除
- Vue.js 2.x 對數組的索引和 length 屬性也監聽不到
除了響應式系統的更新,Vue.js 3.x 通過優化編譯的過程和重寫虛拟 DOM 讓首次渲染和更新的性能有了大幅度提升
編譯優化
Vue.js 2.x 模闆首先需要編譯成 render 函數,這個過程一般在建構時完成的,在編譯時會編譯靜态根節點和靜态節點,靜态根節點要求節點中必須有一個靜态子節點
當元件的狀态發生變化後會通知 watcher 觸發 update 去執行 虛拟DOM 的 patch 操作,周遊所有的虛拟節點找到差異更新到 真實DOM 上,diff 的過程中會去比較整個 虛拟DOM,先對比新舊 div 以及它的屬性再對比内部子節點
Vue.js 2.x 中渲染最小的機關是元件,diff 的過程會跳過靜态根節點,因為靜态根節點的内容不會發生變化,即
- Vue.js 2.x 中通過标記靜态根節點,優化 diff 的過程,但是靜态節點還需要進行 diff,沒有被優化
- Vue.js 3.x 中标記和提升所有靜态根節點,diff 的時候隻需要對比動态節點内容
- Fragments(VS Code需要更新 vetur 插件):模闆中不需要再建立唯一的根節點,可以直接放文本内容或者多個同級标簽
- 靜态提升
- Patch flag
- 緩存事件處理函數
優化打包體積
- Vue.js 3.x 中移除了一些不常用的 API
- 例如:inline-template、filter 等
- Tree-shaking
15.5 Vite
随着 Vue 3.x 的釋出,官方還提供了一個開發工具 Vite,使用 Vite 在開發階段測試項目的時候不需要打包直接運作項目,提升了開發的效率
ES Module
- 現代浏覽器都支援 ES Module(IE 不支援)
- 通過下面的方式加載子產品
-
<script type="module" src=""></script>
-
- 支援子產品的 script 預設延遲加載
- 類似于 script 标簽設定 defer
- 在文檔解析完成後,觸發 DOMContentLoaded 事件前執行
Vite as Vue-CLI 開發環境
- Vite 在開發模式下不需要打包可以直接運作
在開發模式下,Vite 使用浏覽器原生支援的 ES Module 加載子產品,也就是通過 import 導入子產品,支援 ES Module 的現代浏覽器通過
<script type="module" src=""></script>
加載子產品代碼
因為 Vite 不需要打包項目,是以其在開發模式下打開頁面是秒開的
- Vue-CLI 在開發模式下必須對項目打包才可以運作
Vue-CLI 在開發環境下會打包整個項目,如果項目比較大速度會特别慢
Vue 會開啟一個測試的伺服器,它會攔截浏覽器發送的請求,浏覽器會向伺服器發送請求擷取相應的子產品,Vite 會對浏覽器未識别的子產品進行處理,使用這種方式讓 Vite 有以下特點:
Vite 特點
- 快速冷啟動
- 按需編譯
- 子產品熱更新
Vite as Vue-CLI 生産環境
- Vite 在生産環境下使用 Rollup 打包
- 基于 ES Module 的方式打包
- Vue-CLI 使用 Webpack 打包
Vite 建立項目
- Vite 建立項目
npm init vite-app <project-name>
cd <project-name>
npm install
npm run dev
- 基于模闆建立項目
npm init vite-app --template react
npm init vite-app --template preact